primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<help key="Privacy:_Options"/>
<inlinehelp button="show"/>
<fieldset
name="privacy"
label="COM_PRIVACY_OPTION_LABEL"
>
<field
name="notify"
type="integer"
label="COM_PRIVACY_NOTIFY_LABEL"
description="COM_PRIVACY_NOTIFY_DESC"
first="1"
last="29"
step="1"
default="14"
filter="int"
validate="number"
/>
</fieldset>
</config>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<form>
<fields name="filter">
<field
name="search"
type="text"
inputmode="search"
label="COM_PRIVACY_FILTER_SEARCH_LABEL"
description="COM_PRIVACY_SEARCH_IN_USERNAME"
hint="JSEARCH_FILTER"
/>
<field
name="state"
type="list"
label="COM_PRIVACY_CONSENTS_FILTER_STATE"
class="js-select-submit-on-change"
validate="options"
>
<option value="">JOPTION_SELECT_PUBLISHED</option>
<option value="1">COM_PRIVACY_CONSENTS_STATE_VALID</option>
<option value="0">COM_PRIVACY_CONSENTS_STATE_OBSOLETE</option>
<option value="-1">COM_PRIVACY_CONSENTS_STATE_INVALIDATED</option>
</field>
<field
name="subject"
type="sql"
label="COM_PRIVACY_CONSENTS_FILTER_SUBJECT"
sql_select="subject"
sql_from="#__privacy_consents"
sql_group="subject"
sql_order="subject ASC"
key_field="subject"
translate="true"
class="js-select-submit-on-change"
>
<option value="">COM_PRIVACY_CONSENTS_SUBJECT_DEFAULT</option>
</field>
</fields>
<fields name="list">
<field
name="fullordering"
type="list"
label="JGLOBAL_SORT_BY"
class="js-select-submit-on-change"
default="a.id DESC"
validate="options"
>
<option value="a.state ASC">COM_PRIVACY_HEADING_STATUS_ASC</option>
<option value="a.state DESC">COM_PRIVACY_HEADING_STATUS_DESC</option>
<option value="u.name ASC">JGLOBAL_NAME_ASC</option>
<option value="u.name DESC">JGLOBAL_NAME_DESC</option>
<option value="u.username ASC">COM_PRIVACY_HEADING_USERNAME_ASC</option>
<option value="u.username DESC">COM_PRIVACY_HEADING_USERNAME_DESC</option>
<option value="a.user_id ASC">COM_PRIVACY_HEADING_USERID_ASC</option>
<option value="a.user_id DESC">COM_PRIVACY_HEADING_USERID_DESC</option>
<option value="a.subject ASC">COM_PRIVACY_HEADING_SUBJECT_ASC</option>
<option value="a.subject DESC">COM_PRIVACY_HEADING_SUBJECT_DESC</option>
<option value="a.created ASC">COM_PRIVACY_HEADING_CREATED_ASC</option>
<option value="a.created DESC">COM_PRIVACY_HEADING_CREATED_DESC</option>
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
</field>
<field
name="limit"
type="limitbox"
label="JGLOBAL_LIST_LIMIT"
default="25"
class="input-mini js-select-submit-on-change"
/>
</fields>
</form>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<form addfieldprefix="Joomla\Component\Privacy\Administrator\Field">
<fields name="filter">
<field
name="search"
type="text"
inputmode="search"
label="COM_PRIVACY_FILTER_SEARCH_LABEL"
description="COM_PRIVACY_SEARCH_IN_EMAIL"
hint="JSEARCH_FILTER"
/>
<field
name="status"
type="requeststatus"
label="JSTATUS"
class="js-select-submit-on-change"
>
<option value="">JOPTION_SELECT_PUBLISHED</option>
</field>
<field
name="request_type"
type="requesttype"
label="COM_PRIVACY_HEADING_REQUEST_TYPE"
class="js-select-submit-on-change"
>
<option value="">COM_PRIVACY_SELECT_REQUEST_TYPE</option>
</field>
</fields>
<fields name="list">
<field
name="fullordering"
type="list"
label="JGLOBAL_SORT_BY"
class="js-select-submit-on-change"
default="a.id DESC"
validate="options"
>
<option value="">JGLOBAL_SORT_BY</option>
<option value="a.email ASC">COM_PRIVACY_HEADING_EMAIL_ASC</option>
<option value="a.email DESC">COM_PRIVACY_HEADING_EMAIL_DESC</option>
<option value="a.request_type ASC">COM_PRIVACY_HEADING_REQUEST_TYPE_ASC</option>
<option value="a.request_type DESC">COM_PRIVACY_HEADING_REQUEST_TYPE_DESC</option>
<option value="a.requested_at ASC">COM_PRIVACY_HEADING_REQUESTED_AT_ASC</option>
<option value="a.requested_at DESC">COM_PRIVACY_HEADING_REQUESTED_AT_DESC</option>
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
</field>
<field
name="limit"
type="limitbox"
label="JGLOBAL_LIST_LIMIT"
default="25"
class="input-mini js-select-submit-on-change"
/>
</fields>
</form>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<form>
<fieldset>
<field
name="email"
type="email"
label="JGLOBAL_EMAIL"
description="COM_PRIVACY_USER_FIELD_EMAIL_DESC"
required="true"
validate="email"
/>
<field
name="status"
type="list"
label="COM_PRIVACY_FIELD_STATUS_LABEL"
filter="int"
default="0"
validate="options"
readonly="true"
>
<option value="0">COM_PRIVACY_STATUS_PENDING</option>
<option value="-1">COM_PRIVACY_STATUS_INVALID</option>
<option value="1">COM_PRIVACY_STATUS_CONFIRMED</option>
<option value="2">COM_PRIVACY_STATUS_COMPLETED</option>
</field>
<field
name="request_type"
type="list"
label="COM_PRIVACY_FIELD_REQUEST_TYPE_LABEL"
filter="string"
default="export"
validate="options"
>
<option value="export">COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_EXPORT</option>
<option value="remove">COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_REMOVE</option>
</field>
<field
name="id"
type="number"
label="JGLOBAL_FIELD_ID_LABEL"
description="JGLOBAL_FIELD_ID_DESC"
class="readonly"
default="0"
readonly="true"
/>
</fieldset>
</form>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_privacy</name>
<author>Joomla! Project</author>
<creationDate>2018-05</creationDate>
<copyright>(C) 2018 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>3.9.0</version>
<description>COM_PRIVACY_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Component\Privacy</namespace>
<files folder="site">
<folder>forms</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="site">
<language tag="en-GB">language/en-GB/com_privacy.ini</language>
</languages>
<administration>
<files folder="admin">
<filename>config.xml</filename>
<filename>privacy.xml</filename>
<folder>forms</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_privacy.ini</language>
<language tag="en-GB">language/en-GB/com_privacy.sys.ini</language>
</languages>
</administration>
<dashboards>
<dashboard title="COM_PRIVACY_DASHBOARD_TITLE" icon="icon-lock">privacy</dashboard>
</dashboards>
</extension>

View File

@ -0,0 +1,59 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
use Joomla\CMS\HTML\Registry;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\Component\Privacy\Administrator\Extension\PrivacyComponent;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
/**
* The privacy component service provider.
*
* @since 4.0.0
*/
return new class () implements ServiceProviderInterface {
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
*
* @since 4.0.0
*/
public function register(Container $container)
{
$container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Privacy'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Privacy'));
$container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Privacy'));
$container->set(
ComponentInterface::class,
function (Container $container) {
$component = new PrivacyComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
$component->setRegistry($container->get(Registry::class));
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
return $component;
}
);
}
};

View File

@ -0,0 +1,96 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Router\Route;
use Joomla\Component\Privacy\Administrator\Model\ConsentsModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Consents management controller class.
*
* @since 3.9.0
*/
class ConsentsController extends FormController
{
/**
* Method to invalidate specific consents.
*
* @return void
*
* @since 3.9.0
*/
public function invalidate()
{
// Check for request forgeries
$this->checkToken();
$ids = (array) $this->input->get('cid', [], 'int');
// Remove zero values resulting from input filter
$ids = array_filter($ids);
if (empty($ids)) {
$this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), CMSApplication::MSG_ERROR);
} else {
/** @var ConsentsModel $model */
$model = $this->getModel();
if (!$model->invalidate($ids)) {
$this->setMessage($model->getError());
} else {
$this->setMessage(Text::plural('COM_PRIVACY_N_CONSENTS_INVALIDATED', \count($ids)));
}
}
$this->setRedirect(Route::_('index.php?option=com_privacy&view=consents', false));
}
/**
* Method to invalidate all consents of a specific subject.
*
* @return void
*
* @since 3.9.0
*/
public function invalidateAll()
{
// Check for request forgeries
$this->checkToken();
$filters = $this->input->get('filter', [], 'array');
$this->setRedirect(Route::_('index.php?option=com_privacy&view=consents', false));
if (isset($filters['subject']) && $filters['subject'] != '') {
$subject = $filters['subject'];
} else {
$this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'));
return;
}
/** @var ConsentsModel $model */
$model = $this->getModel();
if (!$model->invalidateAll($subject)) {
$this->setMessage($model->getError());
}
$this->setMessage(Text::_('COM_PRIVACY_CONSENTS_INVALIDATED_ALL'));
}
}

View File

@ -0,0 +1,124 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @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\BaseController;
use Joomla\CMS\Response\JsonResponse;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use Joomla\Component\Privacy\Administrator\Model\RequestsModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Privacy Controller
*
* @since 3.9.0
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
* @since 3.9.0
*/
protected $default_view = 'requests';
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters and their variable types.
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
*
* @return $this
*
* @since 3.9.0
*/
public function display($cachable = false, $urlparams = [])
{
// Get the document object.
$document = $this->app->getDocument();
// Set the default view name and format from the Request.
$vName = $this->input->get('view', $this->default_view);
$vFormat = $document->getType();
$lName = $this->input->get('layout', 'default', 'string');
// Get and render the view.
if ($view = $this->getView($vName, $vFormat)) {
$model = $this->getModel($vName);
$view->setModel($model, true);
if ($vName === 'request') {
// For the default layout, we need to also push the action logs model into the view
if ($lName === 'default') {
$logsModel = $this->app->bootComponent('com_actionlogs')
->getMVCFactory()->createModel('Actionlogs', 'Administrator', ['ignore_request' => true]);
// Set default ordering for the context
$logsModel->setState('list.fullordering', 'a.log_date DESC');
// And push the model into the view
$view->setModel($logsModel, false);
}
// For the edit layout, if mail sending is disabled then redirect back to the list view as the form is unusable in this state
if ($lName === 'edit' && !$this->app->get('mailonline', 1)) {
$this->setRedirect(
Route::_('index.php?option=com_privacy&view=requests', false),
Text::_('COM_PRIVACY_WARNING_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED'),
'warning'
);
return $this;
}
}
$view->setLayout($lName);
// Push document object into the view.
$view->document = $document;
$view->display();
}
return $this;
}
/**
* Fetch and report number urgent privacy requests in JSON format, for AJAX requests
*
* @return void
*
* @since 3.9.0
*/
public function getNumberUrgentRequests()
{
// Check for a valid token. If invalid, send a 403 with the error message.
if (!Session::checkToken('get')) {
$this->app->setHeader('status', 403, true);
$this->app->sendHeaders();
echo new JsonResponse(new \Exception(Text::_('JINVALID_TOKEN'), 403));
$this->app->close();
}
/** @var RequestsModel $model */
$model = $this->getModel('requests');
$numberUrgentRequests = $model->getNumberUrgentRequests();
echo new JsonResponse(['number_urgent_requests' => $numberUrgentRequests]);
}
}

View File

@ -0,0 +1,403 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @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);
case '-1':
case '2':
default:
// 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
return false;
}
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Controller;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Requests management controller class.
*
* @since 3.9.0
*/
class RequestsController extends AdminController
{
/**
* 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 BaseDatabaseModel|boolean Model object on success; otherwise false on failure.
*
* @since 3.9.0
*/
public function getModel($name = 'Request', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Dispatcher;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Dispatcher\ComponentDispatcher;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component dispatcher class for com_privacy
*
* @since 4.0.0
*/
class Dispatcher extends ComponentDispatcher
{
/**
* Method to check component access permission
*
* @return void
*/
protected function checkAccess()
{
// Check the user has permission to access this component if in the backend
if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.admin', $this->option)) {
throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
}
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Export;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Data object representing all data contained in a domain.
*
* A domain is typically a single database table and the items within the domain are separate rows from the table.
*
* @since 3.9.0
*/
class Domain
{
/**
* The name of this domain
*
* @var string
* @since 3.9.0
*/
public $name;
/**
* A short description of the data in this domain
*
* @var string
* @since 3.9.0
*/
public $description;
/**
* The items belonging to this domain
*
* @var Item[]
* @since 3.9.0
*/
protected $items = [];
/**
* Add an item to the domain
*
* @param Item $item The item to add
*
* @return void
*
* @since 3.9.0
*/
public function addItem(Item $item)
{
$this->items[] = $item;
}
/**
* Get the domain's items
*
* @return Item[]
*
* @since 3.9.0
*/
public function getItems()
{
return $this->items;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Export;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Data object representing a field within an item.
*
* @since 3.9.0
*/
class Field
{
/**
* The name of this field
*
* @var string
* @since 3.9.0
*/
public $name;
/**
* The field's value
*
* @var mixed
* @since 3.9.0
*/
public $value;
}

View File

@ -0,0 +1,67 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Export;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Data object representing a single item within a domain.
*
* An item is typically a single row from a database table.
*
* @since 3.9.0
*/
class Item
{
/**
* The primary identifier of this item, typically the primary key for a database row.
*
* @var integer
* @since 3.9.0
*/
public $id;
/**
* The fields belonging to this item
*
* @var Field[]
* @since 3.9.0
*/
protected $fields = [];
/**
* Add a field to the item
*
* @param Field $field The field to add
*
* @return void
*
* @since 3.9.0
*/
public function addField(Field $field)
{
$this->fields[] = $field;
}
/**
* Get the item's fields
*
* @return Field[]
*
* @since 3.9.0
*/
public function getFields()
{
return $this->fields;
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Extension;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Joomla\Component\Privacy\Administrator\Service\HTML\Privacy;
use Psr\Container\ContainerInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component class for com_privacy
*
* @since 4.0.0
*/
class PrivacyComponent extends MVCComponent implements BootableExtensionInterface, RouterServiceInterface
{
use HTMLRegistryAwareTrait;
use RouterServiceTrait;
/**
* Booting the extension. This is the function to set up the environment of the extension like
* registering new class loaders, etc.
*
* If required, some initial set up can be done from services of the container, eg.
* registering HTML services.
*
* @param ContainerInterface $container The container
*
* @return void
*
* @since 4.0.0
*/
public function boot(ContainerInterface $container)
{
$this->getRegistry()->register('privacy', new Privacy());
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Field;
use Joomla\CMS\Form\Field\PredefinedlistField;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Form Field to load a list of request statuses
*
* @since 3.9.0
*/
class RequeststatusField extends PredefinedlistField
{
/**
* The form field type.
*
* @var string
* @since 3.9.0
*/
public $type = 'RequestStatus';
/**
* Available statuses
*
* @var array
* @since 3.9.0
*/
protected $predefinedOptions = [
'-1' => 'COM_PRIVACY_STATUS_INVALID',
'0' => 'COM_PRIVACY_STATUS_PENDING',
'1' => 'COM_PRIVACY_STATUS_CONFIRMED',
'2' => 'COM_PRIVACY_STATUS_COMPLETED',
];
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Field;
use Joomla\CMS\Form\Field\PredefinedlistField;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Form Field to load a list of request types
*
* @since 3.9.0
*/
class RequesttypeField extends PredefinedlistField
{
/**
* The form field type.
*
* @var string
* @since 3.9.0
*/
public $type = 'RequestType';
/**
* Available types
*
* @var array
* @since 3.9.0
*/
protected $predefinedOptions = [
'export' => 'COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_EXPORT',
'remove' => 'COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_REMOVE',
];
}

View File

@ -0,0 +1,86 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Helper;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\Component\Privacy\Administrator\Export\Domain;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Privacy component helper.
*
* @since 3.9.0
*/
class PrivacyHelper extends ContentHelper
{
/**
* Render the data request as a XML document.
*
* @param Domain[] $exportData The data to be exported.
*
* @return string
*
* @since 3.9.0
*/
public static function renderDataAsXml(array $exportData)
{
$export = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><data-export />');
foreach ($exportData as $domain) {
$xmlDomain = $export->addChild('domain');
$xmlDomain->addAttribute('name', $domain->name);
$xmlDomain->addAttribute('description', $domain->description);
foreach ($domain->getItems() as $item) {
$xmlItem = $xmlDomain->addChild('item');
if ($item->id) {
$xmlItem->addAttribute('id', $item->id);
}
foreach ($item->getFields() as $field) {
$xmlItem->{$field->name} = $field->value;
}
}
}
$dom = new \DOMDocument();
$dom->loadXML($export->asXML());
$dom->formatOutput = true;
return $dom->saveXML();
}
/**
* Gets the privacyconsent system plugin extension id.
*
* @return integer The privacyconsent system plugin extension id.
*
* @since 3.9.2
*/
public static function getPrivacyConsentPluginId()
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
->where($db->quoteName('element') . ' = ' . $db->quote('privacyconsent'));
$db->setQuery($query);
return (int) $db->loadResult();
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Privacy\CollectCapabilitiesEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\BaseModel;
use Joomla\CMS\Plugin\PluginHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Capabilities model class.
*
* @since 3.9.0
*/
class CapabilitiesModel extends BaseModel
{
/**
* Retrieve the extension capabilities.
*
* @return array
*
* @since 3.9.0
*/
public function getCapabilities()
{
$app = Factory::getApplication();
/*
* Capabilities will be collected in two parts:
*
* 1) Core capabilities - This will cover the core API, i.e. all library level classes
* 2) Extension capabilities - This will be collected by a plugin hook to select plugin groups
*
* Plugins which report capabilities should return an associative array with a single root level key which is used as the title
* for the reporting section and an array with each value being a separate capability. All capability messages should be translated
* by the extension when building the array. An example of the structure expected to be returned from plugins can be found in the
* $coreCapabilities array below.
*/
$coreCapabilities = [
Text::_('COM_PRIVACY_HEADING_CORE_CAPABILITIES') => [
Text::_('COM_PRIVACY_CORE_CAPABILITY_SESSION_IP_ADDRESS_AND_COOKIE'),
Text::sprintf('COM_PRIVACY_CORE_CAPABILITY_LOGGING_IP_ADDRESS', $app->get('log_path', JPATH_ADMINISTRATOR . '/logs')),
Text::_('COM_PRIVACY_CORE_CAPABILITY_COMMUNICATION_WITH_JOOMLA_ORG'),
],
];
/*
* We will search for capabilities from the following plugin groups:
*
* - Authentication: These plugins by design process user information and may have capabilities such as creating cookies
* - Captcha: These plugins may communicate information to third party systems
* - Installer: These plugins can add additional install capabilities to the Extension Manager, such as the Install from Web service
* - Privacy: These plugins are the primary integration point into this component
* - User: These plugins are intended to extend the user management system
*
* This is in addition to plugin groups which are imported before this method is triggered, generally this is the system group.
*/
$dispatcher = $app->getDispatcher();
PluginHelper::importPlugin('authentication', null, true, $dispatcher);
PluginHelper::importPlugin('captcha', null, true, $dispatcher);
PluginHelper::importPlugin('installer', null, true, $dispatcher);
PluginHelper::importPlugin('privacy', null, true, $dispatcher);
PluginHelper::importPlugin('user', null, true, $dispatcher);
$pluginResults = $dispatcher->dispatch(
'onPrivacyCollectAdminCapabilities',
new CollectCapabilitiesEvent('onPrivacyCollectAdminCapabilities')
)->getArgument('result', []);
// We are going to "cheat" here and include this component's capabilities without using a plugin
$extensionCapabilities = [
Text::_('COM_PRIVACY') => [
Text::_('COM_PRIVACY_EXTENSION_CAPABILITY_PERSONAL_INFO'),
],
];
foreach ($pluginResults as $pluginResult) {
$extensionCapabilities += $pluginResult;
}
// Sort the extension list alphabetically
ksort($extensionCapabilities);
// Always prepend the core capabilities to the array
return $coreCapabilities + $extensionCapabilities;
}
/**
* Method to auto-populate the model state.
*
* @return void
*
* @since 3.9.0
*/
protected function populateState()
{
// Load the parameters.
$this->setState('params', ComponentHelper::getParams('com_privacy'));
}
}

View File

@ -0,0 +1,226 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\ParameterType;
use Joomla\Database\QueryInterface;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Consents management model class.
*
* @since 3.9.0
*/
class ConsentsModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
*
* @since 3.9.0
*/
public function __construct($config = [])
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'id', 'a.id',
'user_id', 'a.user_id',
'subject', 'a.subject',
'created', 'a.created',
'username', 'u.username',
'name', 'u.name',
'state', 'a.state',
];
}
parent::__construct($config);
}
/**
* Method to get a QueryInterface object for retrieving the data set from a database.
*
* @return QueryInterface
*
* @since 3.9.0
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select($this->getState('list.select', 'a.*'));
$query->from($db->quoteName('#__privacy_consents', 'a'));
// Join over the users for the username and name.
$query->select($db->quoteName('u.username', 'username'))
->select($db->quoteName('u.name', 'name'));
$query->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.user_id');
// Filter by search in email
$search = $this->getState('filter.search');
if (!empty($search)) {
if (stripos($search, 'id:') === 0) {
$ids = (int) substr($search, 3);
$query->where($db->quoteName('a.id') . ' = :id')
->bind(':id', $ids, ParameterType::INTEGER);
} elseif (stripos($search, 'uid:') === 0) {
$uid = (int) substr($search, 4);
$query->where($db->quoteName('a.user_id') . ' = :uid')
->bind(':uid', $uid, ParameterType::INTEGER);
} elseif (stripos($search, 'name:') === 0) {
$search = '%' . substr($search, 5) . '%';
$query->where($db->quoteName('u.name') . ' LIKE :search')
->bind(':search', $search);
} else {
$search = '%' . $search . '%';
$query->where('(' . $db->quoteName('u.username') . ' LIKE :search)')
->bind(':search', $search);
}
}
$state = $this->getState('filter.state');
if ($state != '') {
$state = (int) $state;
$query->where($db->quoteName('a.state') . ' = :state')
->bind(':state', $state, ParameterType::INTEGER);
}
$subject = $this->getState('filter.subject');
if (!empty($subject)) {
$query->where($db->quoteName('a.subject') . ' = :subject')
->bind(':subject', $subject, ParameterType::STRING);
}
// Handle the list ordering.
$ordering = $this->getState('list.ordering');
$direction = $this->getState('list.direction');
if (!empty($ordering)) {
$query->order($db->escape($ordering) . ' ' . $db->escape($direction));
}
return $query;
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string
*
* @since 3.9.0
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('filter.search');
return parent::getStoreId($id);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 3.9.0
*/
protected function populateState($ordering = 'a.id', $direction = 'desc')
{
// Load the parameters.
$this->setState('params', ComponentHelper::getParams('com_privacy'));
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Method to invalidate specific consents.
*
* @param array $pks The ids of the consents to invalidate.
*
* @return boolean True on success.
*/
public function invalidate($pks)
{
// Sanitize the ids.
$pks = (array) $pks;
$pks = ArrayHelper::toInteger($pks);
try {
$db = $this->getDatabase();
$query = $db->getQuery(true)
->update($db->quoteName('#__privacy_consents'))
->set($db->quoteName('state') . ' = -1')
->whereIn($db->quoteName('id'), $pks)
->where($db->quoteName('state') . ' = 1');
$db->setQuery($query);
$db->execute();
} catch (ExecutionFailureException $e) {
$this->setError($e->getMessage());
return false;
}
return true;
}
/**
* Method to invalidate a group of specific consents.
*
* @param array $subject The subject of the consents to invalidate.
*
* @return boolean True on success.
*/
public function invalidateAll($subject)
{
try {
$db = $this->getDatabase();
$query = $db->getQuery(true)
->update($db->quoteName('#__privacy_consents'))
->set($db->quoteName('state') . ' = -1')
->where($db->quoteName('subject') . ' = :subject')
->where($db->quoteName('state') . ' = 1')
->bind(':subject', $subject);
$db->setQuery($query);
$db->execute();
} catch (ExecutionFailureException $e) {
$this->setError($e->getMessage());
return false;
}
return true;
}
}

View File

@ -0,0 +1,335 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Privacy\ExportRequestEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Mail\MailTemplate;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel;
use Joomla\Component\Privacy\Administrator\Export\Domain;
use Joomla\Component\Privacy\Administrator\Helper\PrivacyHelper;
use Joomla\Component\Privacy\Administrator\Table\RequestTable;
use PHPMailer\PHPMailer\Exception as phpmailerException;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Export model class.
*
* @since 3.9.0
*/
class ExportModel extends BaseDatabaseModel implements UserFactoryAwareInterface
{
use UserFactoryAwareTrait;
/**
* Create the export document for an information request.
*
* @param integer $id The request ID to process
*
* @return Domain[]|boolean A SimpleXMLElement object for a successful export or boolean false on an error
*
* @since 3.9.0
*/
public function collectDataForExportRequest($id = null)
{
$id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
if (!$id) {
$this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_EXPORT'));
return false;
}
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
if ($table->request_type !== 'export') {
$this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_EXPORT'));
return false;
}
if ($table->status != 1) {
$this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_EXPORT_UNCONFIRMED_REQUEST'));
return false;
}
// If there is a user account associated with the email address, load it here for use in the plugins
$db = $this->getDatabase();
$userId = (int) $db->setQuery(
$db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
->bind(':email', $table->email)
->setLimit(1)
)->loadResult();
$user = $userId ? $this->getUserFactory()->loadUserById($userId) : null;
// Log the export
$this->logExport($table);
$dispatcher = $this->getDispatcher();
PluginHelper::importPlugin('privacy', null, true, $dispatcher);
$pluginResults = $dispatcher->dispatch('onPrivacyExportRequest', new ExportRequestEvent('onPrivacyExportRequest', [
'subject' => $table,
'user' => $user,
]))->getArgument('result', []);
$domains = [];
foreach ($pluginResults as $pluginDomains) {
$domains = array_merge($domains, $pluginDomains);
}
return $domains;
}
/**
* Email the data export to the user.
*
* @param integer $id The request ID to process
*
* @return boolean
*
* @since 3.9.0
*/
public function emailDataExport($id = null)
{
$id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
if (!$id) {
$this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_EXPORT'));
return false;
}
$exportData = $this->collectDataForExportRequest($id);
if ($exportData === false) {
// Error is already set, we just need to bail
return false;
}
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
if ($table->request_type !== 'export') {
$this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_EXPORT'));
return false;
}
if ($table->status != 1) {
$this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_EXPORT_UNCONFIRMED_REQUEST'));
return false;
}
// Log the email
$this->logExportEmailed($table);
/*
* If there is an associated user account, we will attempt to send this email in the user's preferred language.
* Because of this, it is expected that Language::_() is directly called and that the Text class is NOT used
* for translating all messages.
*
* Error messages will still be displayed to the administrator, so those messages should continue to use the Text class.
*/
$lang = Factory::getLanguage();
$db = $this->getDatabase();
$userId = (int) $db->setQuery(
$db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
->bind(':email', $table->email),
0,
1
)->loadResult();
if ($userId) {
$receiver = $this->getUserFactory()->loadUserById($userId);
/*
* We don't know if the user has admin access, so we will check if they have an admin language in their parameters,
* falling back to the site language, falling back to the currently active language
*/
$langCode = $receiver->getParam('admin_language', '');
if (!$langCode) {
$langCode = $receiver->getParam('language', $lang->getTag());
}
$lang = Language::getInstance($langCode, $lang->getDebug());
}
// Ensure the right language files have been loaded
$lang->load('com_privacy', JPATH_ADMINISTRATOR)
|| $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
// The mailer can be set to either throw Exceptions or return boolean false, account for both
try {
$app = Factory::getApplication();
$mailer = new MailTemplate('com_privacy.userdataexport', $app->getLanguage()->getTag());
$templateData = [
'sitename' => $app->get('sitename'),
'url' => Uri::root(),
];
$mailer->addRecipient($table->email);
$mailer->addTemplateData($templateData);
$mailer->addAttachment('user-data_' . Uri::getInstance()->toString(['host']) . '.xml', PrivacyHelper::renderDataAsXml($exportData));
if ($mailer->send() === false) {
$this->setError($mailer->ErrorInfo);
return false;
}
return true;
} catch (phpmailerException $exception) {
$this->setError($exception->getMessage());
return false;
}
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $name The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $options Configuration array for model. Optional.
*
* @return Table A Table object
*
* @throws \Exception
* @since 3.9.0
*/
public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
{
return parent::getTable($name, $prefix, $options);
}
/**
* Log the data export to the action log system.
*
* @param RequestTable $request The request record being processed
*
* @return void
*
* @since 3.9.0
*/
public function logExport(RequestTable $request)
{
$user = $this->getCurrentUser();
$message = [
'action' => 'export',
'id' => $request->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_EXPORT', 'com_privacy.request', $user->id);
}
/**
* Log the data export email to the action log system.
*
* @param RequestTable $request The request record being processed
*
* @return void
*
* @since 3.9.0
*/
public function logExportEmailed(RequestTable $request)
{
$user = $this->getCurrentUser();
$message = [
'action' => 'export_emailed',
'id' => $request->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_EXPORT_EMAILED', 'com_privacy.request', $user->id);
}
/**
* Method to auto-populate the model state.
*
* @return void
*
* @since 3.9.0
*/
protected function populateState()
{
// Get the pk of the record from the request.
$this->setState($this->getName() . '.request_id', Factory::getApplication()->getInput()->getUint('id'));
// Load the parameters.
$this->setState('params', ComponentHelper::getParams('com_privacy'));
}
/**
* Method to fetch an instance of the action log model.
*
* @return ActionlogModel
*
* @since 4.0.0
*/
private function getActionlogModel(): ActionlogModel
{
return Factory::getApplication()->bootComponent('com_actionlogs')
->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
}
}

View File

@ -0,0 +1,227 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Privacy\CanRemoveDataEvent;
use Joomla\CMS\Event\Privacy\RemoveDataEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel;
use Joomla\Component\Privacy\Administrator\Removal\Status;
use Joomla\Component\Privacy\Administrator\Table\RequestTable;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Remove model class.
*
* @since 3.9.0
*/
class RemoveModel extends BaseDatabaseModel implements UserFactoryAwareInterface
{
use UserFactoryAwareTrait;
/**
* Remove the user data.
*
* @param integer $id The request ID to process
*
* @return boolean
*
* @since 3.9.0
*/
public function removeDataForRequest($id = null)
{
$id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
if (!$id) {
$this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_REMOVE'));
return false;
}
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
if ($table->request_type !== 'remove') {
$this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_REMOVE'));
return false;
}
if ($table->status != 1) {
$this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_REMOVE_UNCONFIRMED_REQUEST'));
return false;
}
// If there is a user account associated with the email address, load it here for use in the plugins
$db = $this->getDatabase();
$userId = (int) $db->setQuery(
$db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
->bind(':email', $table->email)
->setLimit(1)
)->loadResult();
$user = $userId ? $this->getUserFactory()->loadUserById($userId) : null;
$canRemove = true;
$dispatcher = $this->getDispatcher();
PluginHelper::importPlugin('privacy', null, true, $dispatcher);
/** @var Status[] $pluginResults */
$pluginResults = $dispatcher->dispatch('onPrivacyCanRemoveData', new CanRemoveDataEvent('onPrivacyCanRemoveData', [
'subject' => $table,
'user' => $user,
]))->getArgument('result', []);
foreach ($pluginResults as $status) {
if (!$status->canRemove) {
$this->setError($status->reason ?: Text::_('COM_PRIVACY_ERROR_CANNOT_REMOVE_DATA'));
$canRemove = false;
}
}
if (!$canRemove) {
$this->logRemoveBlocked($table, $this->getErrors());
return false;
}
// Log the removal
$this->logRemove($table);
$dispatcher->dispatch('onPrivacyRemoveData', new RemoveDataEvent('onPrivacyRemoveData', [
'subject' => $table,
'user' => $user,
]));
return true;
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $name The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $options Configuration array for model. Optional.
*
* @return Table A Table object
*
* @throws \Exception
* @since 3.9.0
*/
public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
{
return parent::getTable($name, $prefix, $options);
}
/**
* Log the data removal to the action log system.
*
* @param RequestTable $request The request record being processed
*
* @return void
*
* @since 3.9.0
*/
public function logRemove(RequestTable $request)
{
$user = $this->getCurrentUser();
$message = [
'action' => 'remove',
'id' => $request->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_REMOVE', 'com_privacy.request', $user->id);
}
/**
* Log the data removal being blocked to the action log system.
*
* @param RequestTable $request The request record being processed
* @param string[] $reasons The reasons given why the record could not be removed.
*
* @return void
*
* @since 3.9.0
*/
public function logRemoveBlocked(RequestTable $request, array $reasons)
{
$user = $this->getCurrentUser();
$message = [
'action' => 'remove-blocked',
'id' => $request->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'reasons' => implode('; ', $reasons),
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_REMOVE_BLOCKED', 'com_privacy.request', $user->id);
}
/**
* Method to auto-populate the model state.
*
* @return void
*
* @since 3.9.0
*/
protected function populateState()
{
// Get the pk of the record from the request.
$this->setState($this->getName() . '.request_id', Factory::getApplication()->getInput()->getUint('id'));
// Load the parameters.
$this->setState('params', ComponentHelper::getParams('com_privacy'));
}
/**
* Method to fetch an instance of the action log model.
*
* @return ActionlogModel
*
* @since 4.0.0
*/
private function getActionlogModel(): ActionlogModel
{
return Factory::getApplication()->bootComponent('com_actionlogs')
->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
}
}

View File

@ -0,0 +1,446 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Model;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Mail\Exception\MailDisabledException;
use Joomla\CMS\Mail\MailTemplate;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\CMS\User\UserHelper;
use Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel;
use Joomla\Component\Privacy\Administrator\Table\RequestTable;
use Joomla\Database\Exception\ExecutionFailureException;
use PHPMailer\PHPMailer\Exception as phpmailerException;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Request item model class.
*
* @since 3.9.0
*/
class RequestModel extends AdminModel implements UserFactoryAwareInterface
{
use UserFactoryAwareTrait;
/**
* Clean the cache
*
* @param string $group The cache group
*
* @return void
*
* @since 3.9.0
*/
protected function cleanCache($group = 'com_privacy')
{
parent::cleanCache('com_privacy');
}
/**
* Method for getting the form from the model.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return Form|boolean A Form object on success, false on failure
*
* @since 3.9.0
*/
public function getForm($data = [], $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_privacy.request', 'request', ['control' => 'jform', 'load_data' => $loadData]);
if (empty($form)) {
return false;
}
return $form;
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $name The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $options Configuration array for model. Optional.
*
* @return Table A Table object
*
* @throws \Exception
* @since 3.9.0
*/
public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
{
return parent::getTable($name, $prefix, $options);
}
/**
* Method to get the data that should be injected in the form.
*
* @return array The default data is an empty array.
*
* @since 3.9.0
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = Factory::getApplication()->getUserState('com_privacy.edit.request.data', []);
if (empty($data)) {
$data = $this->getItem();
}
return $data;
}
/**
* Log the completion of a request to the action log system.
*
* @param integer $id The ID of the request to process.
*
* @return boolean
*
* @since 3.9.0
*/
public function logRequestCompleted($id)
{
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
$user = $this->getCurrentUser();
$message = [
'action' => 'request-completed',
'requesttype' => $table->request_type,
'subjectemail' => $table->email,
'id' => $table->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_COMPLETED_REQUEST', 'com_privacy.request', $user->id);
return true;
}
/**
* Log the creation of a request to the action log system.
*
* @param integer $id The ID of the request to process.
*
* @return boolean
*
* @since 3.9.0
*/
public function logRequestCreated($id)
{
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
$user = $this->getCurrentUser();
$message = [
'action' => 'request-created',
'requesttype' => $table->request_type,
'subjectemail' => $table->email,
'id' => $table->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_CREATED_REQUEST', 'com_privacy.request', $user->id);
return true;
}
/**
* Log the invalidation of a request to the action log system.
*
* @param integer $id The ID of the request to process.
*
* @return boolean
*
* @since 3.9.0
*/
public function logRequestInvalidated($id)
{
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
$user = $this->getCurrentUser();
$message = [
'action' => 'request-invalidated',
'requesttype' => $table->request_type,
'subjectemail' => $table->email,
'id' => $table->id,
'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_INVALIDATED_REQUEST', 'com_privacy.request', $user->id);
return true;
}
/**
* Notifies the user that an information request has been created by a site administrator.
*
* Because confirmation tokens are stored in the database as a hashed value, this method will generate a new confirmation token
* for the request.
*
* @param integer $id The ID of the request to process.
*
* @return boolean
*
* @since 3.9.0
*/
public function notifyUserAdminCreatedRequest($id)
{
/** @var RequestTable $table */
$table = $this->getTable();
if (!$table->load($id)) {
$this->setError($table->getError());
return false;
}
/*
* If there is an associated user account, we will attempt to send this email in the user's preferred language.
* Because of this, it is expected that Language::_() is directly called and that the Text class is NOT used
* for translating all messages.
*
* Error messages will still be displayed to the administrator, so those messages should continue to use the Text class.
*/
$lang = Factory::getLanguage();
$db = $this->getDatabase();
$userId = (int) $db->setQuery(
$db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
->bind(':email', $table->email)
->setLimit(1)
)->loadResult();
if ($userId) {
$receiver = $this->getUserFactory()->loadUserById($userId);
/*
* We don't know if the user has admin access, so we will check if they have an admin language in their parameters,
* falling back to the site language, falling back to the currently active language
*/
$langCode = $receiver->getParam('admin_language', '');
if (!$langCode) {
$langCode = $receiver->getParam('language', $lang->getTag());
}
$lang = Language::getInstance($langCode, $lang->getDebug());
}
// Ensure the right language files have been loaded
$lang->load('com_privacy', JPATH_ADMINISTRATOR)
|| $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
// Regenerate the confirmation token
$token = ApplicationHelper::getHash(UserHelper::genRandomPassword());
$hashedToken = UserHelper::hashPassword($token);
$table->confirm_token = $hashedToken;
$table->confirm_token_created_at = Factory::getDate()->toSql();
try {
$table->store();
} catch (ExecutionFailureException $exception) {
$this->setError($exception->getMessage());
return false;
}
// The mailer can be set to either throw Exceptions or return boolean false, account for both
try {
$app = Factory::getApplication();
$linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
$templateData = [
'sitename' => $app->get('sitename'),
'url' => Uri::root(),
'tokenurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm&confirm_token=' . $token, false, $linkMode, true),
'formurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm', false, $linkMode, true),
'token' => $token,
];
switch ($table->request_type) {
case 'export':
$mailer = new MailTemplate('com_privacy.notification.admin.export', $app->getLanguage()->getTag());
break;
case 'remove':
$mailer = new MailTemplate('com_privacy.notification.admin.remove', $app->getLanguage()->getTag());
break;
default:
$this->setError(Text::_('COM_PRIVACY_ERROR_UNKNOWN_REQUEST_TYPE'));
return false;
}
$mailer->addTemplateData($templateData);
$mailer->addRecipient($table->email);
$mailer->send();
return true;
} catch (MailDisabledException | phpmailerException $exception) {
$this->setError($exception->getMessage());
return false;
}
}
/**
* Method to save the form data.
*
* @param array $data The form data.
*
* @return boolean True on success, False on error.
*
* @since 3.9.0
*/
public function save($data)
{
$table = $this->getTable();
$key = $table->getKeyName();
$pk = !empty($data[$key]) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
if (!$pk && !Factory::getApplication()->get('mailonline', 1)) {
$this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED'));
return false;
}
return parent::save($data);
}
/**
* Method to validate the form data.
*
* @param Form $form The form to validate against.
* @param array $data The data to validate.
* @param string $group The name of the field group to validate.
*
* @return array|boolean Array of filtered data if valid, false otherwise.
*
* @see \Joomla\CMS\Form\FormRule
* @see \Joomla\CMS\Filter\InputFilter
* @since 3.9.0
*/
public function validate($form, $data, $group = null)
{
$validatedData = parent::validate($form, $data, $group);
// If parent validation failed there's no point in doing our extended validation
if ($validatedData === false) {
return false;
}
// Make sure the status is always 0
$validatedData['status'] = 0;
// The user cannot create a request for their own account
if (strtolower($this->getCurrentUser()->email) === strtolower($validatedData['email'])) {
$this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_FOR_SELF'));
return false;
}
// Check for an active request for this email address
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('COUNT(id)')
->from($db->quoteName('#__privacy_requests'))
->where($db->quoteName('email') . ' = :email')
->where($db->quoteName('request_type') . ' = :requesttype')
->whereIn($db->quoteName('status'), [0, 1])
->bind(':email', $validatedData['email'])
->bind(':requesttype', $validatedData['request_type']);
$activeRequestCount = (int) $db->setQuery($query)->loadResult();
if ($activeRequestCount > 0) {
$this->setError(Text::_('COM_PRIVACY_ERROR_ACTIVE_REQUEST_FOR_EMAIL'));
return false;
}
return $validatedData;
}
/**
* Method to fetch an instance of the action log model.
*
* @return ActionlogModel
*
* @since 4.0.0
*/
private function getActionlogModel(): ActionlogModel
{
return Factory::getApplication()->bootComponent('com_actionlogs')
->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
}
}

View File

@ -0,0 +1,181 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\ParameterType;
use Joomla\Database\QueryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Requests management model class.
*
* @since 3.9.0
*/
class RequestsModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
*
* @since 3.9.0
*/
public function __construct($config = [])
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'id', 'a.id',
'email', 'a.email',
'requested_at', 'a.requested_at',
'request_type', 'a.request_type',
'status', 'a.status',
];
}
parent::__construct($config);
}
/**
* Method to get a QueryInterface object for retrieving the data set from a database.
*
* @return QueryInterface
*
* @since 3.9.0
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select($this->getState('list.select', 'a.*'));
$query->from($db->quoteName('#__privacy_requests', 'a'));
// Filter by status
$status = $this->getState('filter.status');
if (is_numeric($status)) {
$status = (int) $status;
$query->where($db->quoteName('a.status') . ' = :status')
->bind(':status', $status, ParameterType::INTEGER);
}
// Filter by request type
$requestType = $this->getState('filter.request_type', '');
if ($requestType) {
$query->where($db->quoteName('a.request_type') . ' = :requesttype')
->bind(':requesttype', $requestType);
}
// Filter by search in email
$search = $this->getState('filter.search');
if (!empty($search)) {
if (stripos($search, 'id:') === 0) {
$ids = (int) substr($search, 3);
$query->where($db->quoteName('a.id') . ' = :id')
->bind(':id', $ids, ParameterType::INTEGER);
} else {
$search = '%' . $search . '%';
$query->where('(' . $db->quoteName('a.email') . ' LIKE :search)')
->bind(':search', $search);
}
}
// Handle the list ordering.
$ordering = $this->getState('list.ordering');
$direction = $this->getState('list.direction');
if (!empty($ordering)) {
$query->order($db->escape($ordering) . ' ' . $db->escape($direction));
}
return $query;
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string
*
* @since 3.9.0
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.status');
$id .= ':' . $this->getState('filter.request_type');
return parent::getStoreId($id);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 3.9.0
*/
protected function populateState($ordering = 'a.id', $direction = 'desc')
{
// Load the parameters.
$this->setState('params', ComponentHelper::getParams('com_privacy'));
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Method to return number privacy requests older than X days.
*
* @return integer
*
* @since 3.9.0
*/
public function getNumberUrgentRequests()
{
// Load the parameters.
$params = ComponentHelper::getComponent('com_privacy')->getParams();
$notify = (int) $params->get('notify', 14);
$now = Factory::getDate()->toSql();
$period = '-' . $notify;
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('COUNT(*)');
$query->from($db->quoteName('#__privacy_requests'));
$query->where($db->quoteName('status') . ' = 1 ');
$query->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('requested_at'));
$db->setQuery($query);
return (int) $db->loadResult();
}
}

View File

@ -0,0 +1,170 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Plugin;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Table\Table;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\Component\Privacy\Administrator\Export\Domain;
use Joomla\Component\Privacy\Administrator\Export\Field;
use Joomla\Component\Privacy\Administrator\Export\Item;
use Joomla\Database\DatabaseAwareTrait;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Base class for privacy plugins
*
* @since 3.9.0
*/
abstract class PrivacyPlugin extends CMSPlugin
{
use DatabaseAwareTrait;
/**
* Database object
*
* @var \Joomla\Database\DatabaseDriver
* @since 3.9.0
* @deprecated 4.4.0 will be removed in 6.0 use $this->getDatabase() instead
*/
protected $db;
/**
* Affects constructor behaviour. If true, language files will be loaded automatically.
*
* @var boolean
* @since 3.9.0
*/
protected $autoloadLanguage = true;
/**
* Create a new domain object
*
* @param string $name The domain's name
* @param string $description The domain's description
*
* @return Domain
*
* @since 3.9.0
*/
protected function createDomain($name, $description = '')
{
$domain = new Domain();
$domain->name = $name;
$domain->description = $description;
return $domain;
}
/**
* Create an item object for an array
*
* @param array $data The array data to convert
* @param integer|null $itemId The ID of this item
*
* @return Item
*
* @since 3.9.0
*/
protected function createItemFromArray(array $data, $itemId = null)
{
$item = new Item();
$item->id = $itemId;
foreach ($data as $key => $value) {
if (\is_object($value)) {
$value = (array) $value;
}
if (\is_array($value)) {
$value = print_r($value, true);
}
$field = new Field();
$field->name = $key;
$field->value = $value;
$item->addField($field);
}
return $item;
}
/**
* Create an item object for a Table object
*
* @param Table $table The Table object to convert
*
* @return Item
*
* @since 3.9.0
*/
protected function createItemForTable($table)
{
$data = [];
foreach (array_keys($table->getFields()) as $fieldName) {
$data[$fieldName] = $table->$fieldName;
}
return $this->createItemFromArray($data, $table->{$table->getKeyName(false)});
}
/**
* Helper function to create the domain for the items custom fields.
*
* @param string $context The context
* @param array $items The items
*
* @return Domain
*
* @since 3.9.0
*/
protected function createCustomFieldsDomain($context, $items = [])
{
if (!\is_array($items)) {
$items = [$items];
}
$parts = FieldsHelper::extract($context);
if (!$parts) {
return [];
}
$type = str_replace('com_', '', $parts[0]);
$domain = $this->createDomain($type . '_' . $parts[1] . '_custom_fields', 'joomla_' . $type . '_' . $parts[1] . '_custom_fields_data');
foreach ($items as $item) {
// Get item's fields, also preparing their value property for manual display
$fields = FieldsHelper::getFields($parts[0] . '.' . $parts[1], $item);
foreach ($fields as $field) {
$fieldValue = \is_array($field->value) ? implode(', ', $field->value) : $field->value;
$data = [
$type . '_id' => $item->id,
'field_name' => $field->name,
'field_title' => $field->title,
'field_value' => $fieldValue,
];
$domain->addItem($this->createItemFromArray($data));
}
}
return $domain;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Removal;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Data object communicating the status of whether the data for an information request can be removed.
*
* Typically, this object will only be used to communicate data will be removed.
*
* @since 3.9.0
*/
class Status
{
/**
* Flag indicating the status reported by the plugin on whether the information can be removed
*
* @var boolean
* @since 3.9.0
*/
public $canRemove = true;
/**
* A status message indicating the reason data can or cannot be removed
*
* @var string
* @since 3.9.0
*/
public $reason;
}

View File

@ -0,0 +1,52 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Service\HTML;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Privacy component HTML helper.
*
* @since 3.9.0
*/
class Privacy
{
/**
* Render a status badge
*
* @param integer $status The item status
*
* @return string
*
* @since 3.9.0
*/
public function statusLabel($status)
{
switch ($status) {
case 2:
return '<span class="badge bg-success">' . Text::_('COM_PRIVACY_STATUS_COMPLETED') . '</span>';
case 1:
return '<span class="badge bg-info">' . Text::_('COM_PRIVACY_STATUS_CONFIRMED') . '</span>';
case -1:
return '<span class="badge bg-danger">' . Text::_('COM_PRIVACY_STATUS_INVALID') . '</span>';
default:
case 0:
return '<span class="badge bg-warning">' . Text::_('COM_PRIVACY_STATUS_PENDING') . '</span>';
}
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Table;
use Joomla\CMS\Factory;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
use Joomla\Event\DispatcherInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Table interface class for the #__privacy_consents table
*
* @property integer $id Item ID (primary key)
* @property integer $remind The status of the reminder request
* @property string $token Hashed token for the reminder request
* @property integer $user_id User ID (pseudo foreign key to the #__users table) if the request is associated to a user account
*
* @since 3.9.0
*/
class ConsentTable extends Table
{
/**
* The class constructor.
*
* @param DatabaseDriver $db Database connector object
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
*
* @since 3.9.0
*/
public function __construct(DatabaseDriver $db, ?DispatcherInterface $dispatcher = null)
{
parent::__construct('#__privacy_consents', 'id', $db, $dispatcher);
}
/**
* Method to store a row in the database from the Table instance properties.
*
* @param boolean $updateNulls True to update fields even if they are null.
*
* @return boolean True on success.
*
* @since 3.9.0
*/
public function store($updateNulls = false)
{
$date = Factory::getDate();
// Set default values for new records
if (!$this->id) {
if (!$this->remind) {
$this->remind = '0';
}
if (!$this->created) {
$this->created = $date->toSql();
}
}
return parent::store($updateNulls);
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\Table;
use Joomla\CMS\Factory;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
use Joomla\Event\DispatcherInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Table interface class for the #__privacy_requests table
*
* @property integer $id Item ID (primary key)
* @property string $email The email address of the individual requesting the data
* @property string $requested_at The time the request was created at
* @property integer $status The status of the information request
* @property string $request_type The type of information request
* @property string $confirm_token Hashed token for confirming the information request
* @property string $confirm_token_created_at The time the confirmation token was generated
*
* @since 3.9.0
*/
class RequestTable extends Table
{
/**
* Indicates that columns fully support the NULL value in the database
*
* @var boolean
* @since 4.0.0
*/
protected $_supportNullValue = true;
/**
* The class constructor.
*
* @param DatabaseDriver $db Database connector object
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
*
* @since 3.9.0
*/
public function __construct(DatabaseDriver $db, ?DispatcherInterface $dispatcher = null)
{
parent::__construct('#__privacy_requests', 'id', $db, $dispatcher);
}
/**
* Method to store a row in the database from the Table instance properties.
*
* @param boolean $updateNulls True to update fields even if they are null.
*
* @return boolean True on success.
*
* @since 3.9.0
*/
public function store($updateNulls = true)
{
$date = Factory::getDate();
// Set default values for new records
if (!$this->id) {
if (!$this->status) {
$this->status = '0';
}
if (!$this->requested_at) {
$this->requested_at = $date->toSql();
}
if (!$this->confirm_token_created_at) {
$this->confirm_token_created_at = null;
}
}
return parent::store($updateNulls);
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\View\Capabilities;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Capabilities view class
*
* @since 3.9.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The reported extension capabilities
*
* @var array
* @since 3.9.0
*/
protected $capabilities;
/**
* The state information
*
* @var \Joomla\Registry\Registry
* @since 3.9.0
*/
protected $state;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @see BaseHtmlView::loadTemplate()
* @since 3.9.0
* @throws \Exception
*/
public function display($tpl = null)
{
// Initialise variables
$this->capabilities = $this->get('Capabilities');
$this->state = $this->get('State');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new Genericdataexception(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.9.0
*/
protected function addToolbar()
{
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_CAPABILITIES'), 'lock');
$toolbar->preferences('com_privacy');
$toolbar->help('Privacy:_Extension_Capabilities');
}
}

View File

@ -0,0 +1,149 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\View\Consents;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Privacy\Administrator\Model\ConsentsModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Consents view class
*
* @since 3.9.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The active search tools filters
*
* @var array
* @since 3.9.0
* @note Must be public to be accessed from the search tools layout
*/
public $activeFilters;
/**
* Form instance containing the search tools filter form
*
* @var Form
* @since 3.9.0
* @note Must be public to be accessed from the search tools layout
*/
public $filterForm;
/**
* The items to display
*
* @var array
* @since 3.9.0
*/
protected $items;
/**
* The pagination object
*
* @var Pagination
* @since 3.9.0
*/
protected $pagination;
/**
* The state information
*
* @var \Joomla\Registry\Registry
* @since 3.9.0
*/
protected $state;
/**
* Is this view an Empty State
*
* @var boolean
* @since 4.0.0
*/
private $isEmptyState = false;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @see BaseHtmlView::loadTemplate()
* @since 3.9.0
* @throws \Exception
*/
public function display($tpl = null)
{
/** @var ConsentsModel $model */
$model = $this->getModel();
$this->items = $model->getItems();
$this->pagination = $model->getPagination();
$this->state = $model->getState();
$this->filterForm = $model->getFilterForm();
$this->activeFilters = $model->getActiveFilters();
if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
$this->setLayout('emptystate');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new Genericdataexception(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.9.0
*/
protected function addToolbar()
{
ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_CONSENTS'), 'lock');
$toolbar = $this->getDocument()->getToolbar();
// Add a button to invalidate a consent
if (!$this->isEmptyState) {
$toolbar->confirmButton('trash', 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE', 'consents.invalidate')
->message('COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE')
->icon('icon-trash')
->listCheck(true);
}
// If the filter is restricted to a specific subject, show the "Invalidate all" button
if ($this->state->get('filter.subject') != '') {
$toolbar->confirmButton('cancel', 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_ALL', 'consents.invalidateAll')
->message('COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_ALL_CONFIRM_MSG')
->icon('icon-cancel')
->listCheck(false);
}
$toolbar->preferences('com_privacy');
$toolbar->help('Privacy:_Consents');
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\View\Export;
use Joomla\CMS\MVC\View\AbstractView;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\Component\Privacy\Administrator\Helper\PrivacyHelper;
use Joomla\Component\Privacy\Administrator\Model\ExportModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Export view class
*
* @since 3.9.0
*
* @property-read \Joomla\CMS\Document\XmlDocument $document
*/
class XmlView extends AbstractView
{
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @since 3.9.0
* @throws \Exception
*/
public function display($tpl = null)
{
/** @var ExportModel $model */
$model = $this->getModel();
$exportData = $model->collectDataForExportRequest();
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$requestId = $model->getState($model->getName() . '.request_id');
// This document should always be downloaded
$this->getDocument()->setDownload(true);
$this->getDocument()->setName('export-request-' . $requestId);
echo PrivacyHelper::renderDataAsXml($exportData);
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\View\Request;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Privacy\Administrator\Model\RequestModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Request view class
*
* @since 3.9.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The action logs for the item
*
* @var array
* @since 3.9.0
*/
protected $actionlogs;
/**
* The form object
*
* @var Form
* @since 3.9.0
*/
protected $form;
/**
* The item record
*
* @var \stdClass
* @since 3.9.0
*/
protected $item;
/**
* The state information
*
* @var \Joomla\Registry\Registry
* @since 3.9.0
*/
protected $state;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @see BaseHtmlView::loadTemplate()
* @since 3.9.0
* @throws \Exception
*/
public function display($tpl = null)
{
/** @var RequestModel $model */
$model = $this->getModel();
$this->item = $model->getItem();
$this->state = $model->getState();
// Variables only required for the default layout
if ($this->getLayout() === 'default') {
/** @var \Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel $logsModel */
$logsModel = $this->getModel('actionlogs');
$this->actionlogs = $logsModel->getLogsForItem('com_privacy.request', $this->item->id);
// Load the com_actionlogs language strings for use in the layout
$lang = $this->getLanguage();
$lang->load('com_actionlogs', JPATH_ADMINISTRATOR)
|| $lang->load('com_actionlogs', JPATH_ADMINISTRATOR . '/components/com_actionlogs');
}
// Variables only required for the edit layout
if ($this->getLayout() === 'edit') {
$this->form = $this->get('Form');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.9.0
*/
protected function addToolbar()
{
Factory::getApplication()->getInput()->set('hidemainmenu', true);
$toolbar = $this->getDocument()->getToolbar();
// Set the title and toolbar based on the layout
if ($this->getLayout() === 'edit') {
ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUEST_ADD_REQUEST'), 'lock');
$toolbar->save('request.save');
$toolbar->cancel('request.cancel');
$toolbar->help('Privacy:_New_Information_Request');
} else {
ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUEST_SHOW_REQUEST'), 'lock');
// Add transition and action buttons based on item status
switch ($this->item->status) {
case '0':
$toolbar->standardButton('cancel', 'COM_PRIVACY_TOOLBAR_INVALIDATE', 'request.invalidate')
->listCheck(false)
->icon('icon-cancel-circle');
break;
case '1':
$return = '&return=' . base64_encode('index.php?option=com_privacy&view=request&id=' . (int) $this->item->id);
$toolbar->standardButton('apply', 'COM_PRIVACY_TOOLBAR_COMPLETE', 'request.complete')
->listCheck(false)
->icon('icon-apply');
$toolbar->standardButton('invalidate', 'COM_PRIVACY_TOOLBAR_INVALIDATE', 'request.invalidate')
->listCheck(false)
->icon('icon-cancel-circle');
if ($this->item->request_type === 'export') {
$toolbar->linkButton('download', 'COM_PRIVACY_ACTION_EXPORT_DATA')
->url(Route::_('index.php?option=com_privacy&task=request.export&format=xml&id=' . (int) $this->item->id . $return));
if (Factory::getApplication()->get('mailonline', 1)) {
$toolbar->linkButton('mail', 'COM_PRIVACY_ACTION_EMAIL_EXPORT_DATA')
->url(Route::_('index.php?option=com_privacy&task=request.emailexport&id=' . (int) $this->item->id . $return
. '&' . Session::getFormToken() . '=1'));
}
}
if ($this->item->request_type === 'remove') {
$toolbar->standardButton('delete', 'COM_PRIVACY_ACTION_DELETE_DATA', 'request.remove')
->listCheck(false)
->icon('icon-delete');
}
break;
default:
// Item is in a "locked" state and cannot transition
break;
}
$toolbar->cancel('request.cancel');
$toolbar->help('Privacy:_Review_Information_Request');
}
}
}

View File

@ -0,0 +1,150 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Privacy\Administrator\View\Requests;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Privacy\Administrator\Model\RequestsModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Requests view class
*
* @since 3.9.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The active search tools filters
*
* @var array
* @since 3.9.0
* @note Must be public to be accessed from the search tools layout
*/
public $activeFilters;
/**
* Form instance containing the search tools filter form
*
* @var Form
* @since 3.9.0
* @note Must be public to be accessed from the search tools layout
*/
public $filterForm;
/**
* The items to display
*
* @var array
* @since 3.9.0
*/
protected $items;
/**
* The pagination object
*
* @var Pagination
* @since 3.9.0
*/
protected $pagination;
/**
* Flag indicating the site supports sending email
*
* @var boolean
* @since 3.9.0
*/
protected $sendMailEnabled;
/**
* The state information
*
* @var \Joomla\Registry\Registry
* @since 3.9.0
*/
protected $state;
/**
* The age of urgent requests
*
* @var integer
* @since 3.9.0
*/
protected $urgentRequestAge;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @see BaseHtmlView::loadTemplate()
* @since 3.9.0
* @throws \Exception
*/
public function display($tpl = null)
{
/** @var RequestsModel $model */
$model = $this->getModel();
$this->items = $model->getItems();
$this->pagination = $model->getPagination();
$this->state = $model->getState();
$this->filterForm = $model->getFilterForm();
$this->activeFilters = $model->getActiveFilters();
$this->urgentRequestAge = (int) ComponentHelper::getParams('com_privacy')->get('notify', 14);
$this->sendMailEnabled = (bool) Factory::getApplication()->get('mailonline', 1);
if (!\count($this->items) && $this->get('IsEmptyState')) {
$this->setLayout('emptystate');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new Genericdataexception(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.9.0
*/
protected function addToolbar()
{
ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUESTS'), 'lock');
$toolbar = $this->getDocument()->getToolbar();
// Requests can only be created if mail sending is enabled
if (Factory::getApplication()->get('mailonline', 1)) {
$toolbar->addNew('request.add');
}
$toolbar->preferences('com_privacy');
$toolbar->help('Privacy:_Information_Requests');
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
/** @var \Joomla\Component\Privacy\Administrator\View\Capabilities\HtmlView $this */
?>
<div id="j-main-container" class="main-card p-4">
<div class="alert alert-info">
<h2 class="alert-heading"><?php echo Text::_('COM_PRIVACY_MSG_CAPABILITIES_ABOUT_THIS_INFORMATION'); ?></h2>
<?php echo Text::_('COM_PRIVACY_MSG_CAPABILITIES_INTRODUCTION'); ?>
</div>
<?php if (empty($this->capabilities)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('COM_PRIVACY_MSG_CAPABILITIES_NO_CAPABILITIES'); ?>
</div>
<?php else : ?>
<?php foreach ($this->capabilities as $extension => $capabilities) : ?>
<details>
<summary><?php echo $extension; ?></summary>
<?php if (empty($capabilities)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('COM_PRIVACY_MSG_EXTENSION_NO_CAPABILITIES'); ?>
</div>
<?php else : ?>
<ul>
<?php foreach ($capabilities as $capability) : ?>
<li><?php echo $capability; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</details>
<?php endforeach; ?>
<?php endif; ?>
</div>

View File

@ -0,0 +1,130 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
/** @var \Joomla\Component\Privacy\Administrator\View\Consents\HtmlView $this */
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('table.columns')
->useScript('multiselect');
$user = $this->getCurrentUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$now = Factory::getDate();
$stateIcons = [-1 => 'delete', 0 => 'archive', 1 => 'publish'];
$stateMsgs = [
-1 => Text::_('COM_PRIVACY_CONSENTS_STATE_INVALIDATED'),
0 => Text::_('COM_PRIVACY_CONSENTS_STATE_OBSOLETE'),
1 => Text::_('COM_PRIVACY_CONSENTS_STATE_VALID')
];
$this->getLanguage()->load('plg_system_privacyconsent', JPATH_ADMINISTRATOR);
?>
<form action="<?php echo Route::_('index.php?option=com_privacy&view=consents'); ?>" method="post" name="adminForm" id="adminForm">
<div id="j-main-container">
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
<?php if (empty($this->items)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
</div>
<?php else : ?>
<table class="table" id="consentList">
<caption class="visually-hidden">
<?php echo Text::_('COM_PRIVACY_TABLE_CONSENTS_CAPTION'); ?>,
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
</caption>
<thead>
<tr>
<td class="w-1 text-center">
<?php echo HTMLHelper::_('grid.checkall'); ?>
</td>
<th scope="col" class="w-5 text-center">
<?php echo HTMLHelper::_('searchtools.sort', 'JSTATUS', 'a.state', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-10">
<?php echo HTMLHelper::_('searchtools.sort', 'JGLOBAL_USERNAME', 'u.username', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-10">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_PRIVACY_HEADING_NAME', 'u.name', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-1">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_PRIVACY_HEADING_USERID', 'a.user_id', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-10">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_PRIVACY_HEADING_CONSENTS_SUBJECT', 'a.subject', $listDirn, $listOrder); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_PRIVACY_HEADING_CONSENTS_BODY'); ?>
</th>
<th scope="col" class="w-15">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_PRIVACY_HEADING_CONSENTS_CREATED', 'a.created', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-1">
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $i => $item) : ?>
<tr>
<td class="text-center">
<?php echo HTMLHelper::_('grid.id', $i, $item->id, false, 'cid', 'cb', $item->username); ?>
</td>
<td class="tbody-icon">
<span class="icon-<?php echo $stateIcons[$item->state]; ?>" aria-hidden="true" title="<?php echo $stateMsgs[$item->state]; ?>"></span>
<span class="visually-hidden"><?php echo $stateMsgs[$item->state]; ?>"></span>
</td>
<th scope="row">
<?php echo $item->username; ?>
</th>
<td>
<?php echo $item->name; ?>
</td>
<td>
<?php echo $item->user_id; ?>
</td>
<td>
<?php echo Text::_($item->subject); ?>
</td>
<td>
<?php echo $item->body; ?>
</td>
<td class="break-word">
<?php echo HTMLHelper::_('date.relative', new Date($item->created), null, $now); ?>
<div class="small">
<?php echo HTMLHelper::_('date', $item->created, Text::_('DATE_FORMAT_LC6')); ?>
</div>
</td>
<td>
<?php echo (int) $item->id; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<input type="hidden" name="task" value="" />
<input type="hidden" name="boxchecked" value="0" />
<?php echo HTMLHelper::_('form.token'); ?>
</div>
</form>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="COM_PRIVACY_CONSENTS_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_PRIVACY_CONSENTS_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>

View File

@ -0,0 +1,22 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Layout\LayoutHelper;
$displayData = [
'textPrefix' => 'COM_PRIVACY_CONSENTS',
'formURL' => 'index.php?option=com_privacy&view=consents',
'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help5.x:Privacy:_Consents',
'icon' => 'icon-lock',
];
echo LayoutHelper::render('joomla.content.emptystate', $displayData);

View File

@ -0,0 +1,96 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
/** @var \Joomla\Component\Privacy\Administrator\View\Request\HtmlView $this */
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('form.validate');
?>
<form action="<?php echo Route::_('index.php?option=com_privacy&view=request&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="item-form" class="form-validate">
<div class="row mt-3">
<div class="col-12 col-md-4 mb-3">
<div class="card">
<h3 class="card-header"><?php echo Text::_('COM_PRIVACY_HEADING_REQUEST_INFORMATION'); ?></h3>
<div class="card-body">
<dl class="dl-horizontal">
<dt><?php echo Text::_('JGLOBAL_EMAIL'); ?>:</dt>
<dd><?php echo $this->item->email; ?></dd>
<dt><?php echo Text::_('JSTATUS'); ?>:</dt>
<dd><?php echo HTMLHelper::_('privacy.statusLabel', $this->item->status); ?></dd>
<dt><?php echo Text::_('COM_PRIVACY_FIELD_REQUEST_TYPE_LABEL'); ?>:</dt>
<dd><?php echo Text::_('COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_' . $this->item->request_type); ?></dd>
<dt><?php echo Text::_('COM_PRIVACY_FIELD_REQUESTED_AT_LABEL'); ?>:</dt>
<dd><?php echo HTMLHelper::_('date', $this->item->requested_at, Text::_('DATE_FORMAT_LC6')); ?></dd>
</dl>
</div>
</div>
</div>
<div class="col-12 col-md-8 mb-3">
<div class="card">
<h3 class="card-header"><?php echo Text::_('COM_PRIVACY_HEADING_ACTION_LOG'); ?></h3>
<div class="card-body">
<?php if (empty($this->actionlogs)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
</div>
<?php else : ?>
<table class="table">
<caption class="visually-hidden">
<?php echo Text::_('COM_PRIVACY_HEADING_ACTION_LOG'); ?>
</caption>
<thead>
<th scope="col">
<?php echo Text::_('COM_ACTIONLOGS_ACTION'); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_ACTIONLOGS_DATE'); ?>
</th>
<th scope="col">
<?php echo Text::_('COM_ACTIONLOGS_NAME'); ?>
</th>
</thead>
<tbody>
<?php foreach ($this->actionlogs as $i => $item) : ?>
<tr class="row<?php echo $i % 2; ?>">
<th scope="row">
<?php echo ActionlogsHelper::getHumanReadableLogMessage($item); ?>
</th>
<td>
<?php echo HTMLHelper::_('date', $item->log_date, Text::_('DATE_FORMAT_LC6')); ?>
</td>
<td>
<?php echo $item->name; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif;?>
</div>
</div>
</div>
</div>
<input type="hidden" name="task" value="" />
<?php echo HTMLHelper::_('form.token'); ?>
</form>

View File

@ -0,0 +1,41 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/** @var \Joomla\Component\Privacy\Administrator\View\Request\HtmlView $this */
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('form.validate');
?>
<form action="<?php echo Route::_('index.php?option=com_privacy&view=request&layout=edit&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="item-form" aria-label="<?php echo Text::_('COM_PRIVACY_REQUEST_FORM_' . ((int) $this->item->id === 0 ? 'NEW' : 'EDIT'), true); ?>" class="form-validate">
<div class="form-horizontal">
<div class="card mt-3">
<div class="card-body">
<fieldset class="adminform">
<?php echo $this->form->renderField('email'); ?>
<?php echo $this->form->renderField('status'); ?>
<?php echo $this->form->renderField('request_type'); ?>
</fieldset>
</div>
</div>
<input type="hidden" name="task" value="" />
<?php echo HTMLHelper::_('form.token'); ?>
</div>
</form>

View File

@ -0,0 +1,130 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\String\PunycodeHelper;
/** @var \Joomla\Component\Privacy\Administrator\View\Requests\HtmlView $this */
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('table.columns')
->useScript('multiselect');
$user = $this->getCurrentUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$now = Factory::getDate();
$urgentRequestDate = clone $now;
$urgentRequestDate->sub(new DateInterval('P' . $this->urgentRequestAge . 'D'));
?>
<form action="<?php echo Route::_('index.php?option=com_privacy&view=requests'); ?>" method="post" name="adminForm" id="adminForm">
<div id="j-main-container">
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
<?php if (empty($this->items)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
</div>
<?php else : ?>
<table class="table" id="requestList">
<caption class="visually-hidden">
<?php echo Text::_('COM_PRIVACY_TABLE_CAPTION'); ?>,
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
</caption>
<thead>
<tr>
<th scope="col" class="w-5 text-center">
<?php echo Text::_('COM_PRIVACY_HEADING_ACTIONS'); ?>
</th>
<th scope="col" class="w-5 text-center">
<?php echo Text::_('JSTATUS'); ?>
</th>
<th scope="col">
<?php echo HTMLHelper::_('searchtools.sort', 'JGLOBAL_EMAIL', 'a.email', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-10">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_PRIVACY_HEADING_REQUEST_TYPE', 'a.request_type', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-15">
<?php echo HTMLHelper::_('searchtools.sort', 'COM_PRIVACY_HEADING_REQUESTED_AT', 'a.requested_at', $listDirn, $listOrder); ?>
</th>
<th scope="col" class="w-1">
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $i => $item) : ?>
<?php
$itemRequestedAt = new Date($item->requested_at);
?>
<tr>
<td class="text-center">
<div class="btn-group">
<?php if ($item->status == 1 && $item->request_type === 'export') : ?>
<a class="btn tbody-icon" href="<?php echo Route::_('index.php?option=com_privacy&task=request.export&format=xml&id=' . (int) $item->id); ?>" title="<?php echo Text::_('COM_PRIVACY_ACTION_EXPORT_DATA'); ?>"><span class="icon-download" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('COM_PRIVACY_ACTION_EXPORT_DATA'); ?></span></a>
<?php if ($this->sendMailEnabled) : ?>
<a class="btn tbody-icon" href="<?php echo Route::_('index.php?option=com_privacy&task=request.emailexport&id=' . (int) $item->id . '&' . Factory::getSession()->getFormToken() . '=1'); ?>" title="<?php echo Text::_('COM_PRIVACY_ACTION_EMAIL_EXPORT_DATA'); ?>"><span class="icon-mail" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('COM_PRIVACY_ACTION_EMAIL_EXPORT_DATA'); ?></span></a>
<?php endif; ?>
<?php endif; ?>
<?php if ($item->status == 1 && $item->request_type === 'remove') : ?>
<a class="btn tbody-icon" href="<?php echo Route::_('index.php?option=com_privacy&task=request.remove&id=' . (int) $item->id . '&' . Factory::getSession()->getFormToken() . '=1'); ?>" title="<?php echo Text::_('COM_PRIVACY_ACTION_DELETE_DATA'); ?>"><span class="icon-times" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('COM_PRIVACY_ACTION_DELETE_DATA'); ?></span></a>
<?php endif; ?>
</div>
</td>
<td class="text-center">
<?php echo HTMLHelper::_('privacy.statusLabel', $item->status); ?>
</td>
<th scope="row">
<?php if ($item->status == 1 && $urgentRequestDate >= $itemRequestedAt) : ?>
<span class="float-end badge bg-danger"><?php echo Text::_('COM_PRIVACY_BADGE_URGENT_REQUEST'); ?></span>
<?php endif; ?>
<a href="<?php echo Route::_('index.php?option=com_privacy&view=request&id=' . (int) $item->id); ?>" title="<?php echo Text::_('COM_PRIVACY_ACTION_VIEW'); ?>">
<?php echo $this->escape(PunycodeHelper::emailToUTF8($item->email)); ?>
</a>
</th>
<td>
<?php echo Text::_('COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_' . $item->request_type); ?>
</td>
<td>
<?php echo HTMLHelper::_('date.relative', $itemRequestedAt, null, $now); ?>
<div class="small">
<?php echo HTMLHelper::_('date', $item->requested_at, Text::_('DATE_FORMAT_LC6')); ?>
</div>
</td>
<td>
<?php echo (int) $item->id; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php // load the pagination. ?>
<?php echo $this->pagination->getListFooter(); ?>
<?php endif; ?>
<input type="hidden" name="task" value="" />
<input type="hidden" name="boxchecked" value="0" />
<?php echo HTMLHelper::_('form.token'); ?>
</div>
</form>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="COM_PRIVACY_REQUESTS_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_PRIVACY_REQUESTS_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>

View File

@ -0,0 +1,27 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_privacy
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Layout\LayoutHelper;
$displayData = [
'textPrefix' => 'COM_PRIVACY_REQUESTS',
'formURL' => 'index.php?option=com_privacy&view=requests',
'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help5.x:Privacy:_Information_Requests',
'icon' => 'icon-lock',
];
if (Factory::getApplication()->get('mailonline', 1)) {
$displayData['createURL'] = 'index.php?option=com_privacy&task=request.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);