diff --git a/.drone.yml b/.drone.yml index 16a432cf3f..bc72fc785b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -372,6 +372,6 @@ trigger: --- kind: signature -hmac: 6b06b1c7f407650fe98f0851dc865911f399422116fa4f250a52d01a556397ed +hmac: f93ecde6afe7ed244066613dd6b9c10f8139feefd9341b1627fd0a348f8fc802 ... diff --git a/administrator/components/com_actionlogs/config.xml b/administrator/components/com_actionlogs/config.xml index 0a8b4f83bb..b31209a2ba 100644 --- a/administrator/components/com_actionlogs/config.xml +++ b/administrator/components/com_actionlogs/config.xml @@ -30,7 +30,7 @@ label="COM_ACTIONLOGS_LOG_EXTENSIONS_LABEL" multiple="true" layout="joomla.form.field.list-fancy-select" - default="com_banners,com_cache,com_categories,com_checkin,com_config,com_contact,com_content,com_installer,com_media,com_menus,com_messages,com_modules,com_newsfeeds,com_plugins,com_redirect,com_scheduler,com_tags,com_templates,com_users" + default="com_banners,com_cache,com_categories,com_checkin,com_config,com_contact,com_content,com_installer,com_media,com_menus,com_messages,com_modules,com_newsfeeds,com_plugins,com_privacy,com_redirect,com_scheduler,com_tags,com_templates,com_users" /> + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
+ + + + + +
+
diff --git a/administrator/components/com_privacy/config.xml b/administrator/components/com_privacy/config.xml index 33a294ac89..74c903506d 100644 --- a/administrator/components/com_privacy/config.xml +++ b/administrator/components/com_privacy/config.xml @@ -21,4 +21,189 @@ /> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +
+ +
+ +
diff --git a/administrator/components/com_privacy/forms/category.xml b/administrator/components/com_privacy/forms/category.xml new file mode 100644 index 0000000000..b7577debdb --- /dev/null +++ b/administrator/components/com_privacy/forms/category.xml @@ -0,0 +1,20 @@ + +
+ + + +
+ + + + +
+
+ diff --git a/administrator/components/com_privacy/forms/cookie.xml b/administrator/components/com_privacy/forms/cookie.xml new file mode 100644 index 0000000000..73b71ceab8 --- /dev/null +++ b/administrator/components/com_privacy/forms/cookie.xml @@ -0,0 +1,142 @@ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/administrator/components/com_privacy/forms/cookieconsent.xml b/administrator/components/com_privacy/forms/cookieconsent.xml new file mode 100644 index 0000000000..7fcd4145be --- /dev/null +++ b/administrator/components/com_privacy/forms/cookieconsent.xml @@ -0,0 +1,69 @@ + +
+
+ + + + + + + + + + + + + + + +
+
diff --git a/administrator/components/com_privacy/forms/filter_cookieconsents.xml b/administrator/components/com_privacy/forms/filter_cookieconsents.xml new file mode 100644 index 0000000000..8499e45cee --- /dev/null +++ b/administrator/components/com_privacy/forms/filter_cookieconsents.xml @@ -0,0 +1,35 @@ + +
+ + + + + + + + + + + + + +
diff --git a/administrator/components/com_privacy/forms/filter_cookies.xml b/administrator/components/com_privacy/forms/filter_cookies.xml new file mode 100644 index 0000000000..a3fef31d0a --- /dev/null +++ b/administrator/components/com_privacy/forms/filter_cookies.xml @@ -0,0 +1,62 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/administrator/components/com_privacy/forms/filter_scripts.xml b/administrator/components/com_privacy/forms/filter_scripts.xml new file mode 100644 index 0000000000..9a1bb87b57 --- /dev/null +++ b/administrator/components/com_privacy/forms/filter_scripts.xml @@ -0,0 +1,68 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/administrator/components/com_privacy/forms/script.xml b/administrator/components/com_privacy/forms/script.xml new file mode 100644 index 0000000000..17d1ef98cb --- /dev/null +++ b/administrator/components/com_privacy/forms/script.xml @@ -0,0 +1,98 @@ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/administrator/components/com_privacy/services/provider.php b/administrator/components/com_privacy/services/provider.php index d812c3f9ec..0614f90c4f 100644 --- a/administrator/components/com_privacy/services/provider.php +++ b/administrator/components/com_privacy/services/provider.php @@ -13,6 +13,7 @@ use Joomla\CMS\Component\Router\RouterFactoryInterface; use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface; use Joomla\CMS\Extension\ComponentInterface; +use Joomla\CMS\Extension\Service\Provider\CategoryFactory; use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory; use Joomla\CMS\Extension\Service\Provider\MVCFactory; use Joomla\CMS\Extension\Service\Provider\RouterFactory; @@ -39,6 +40,7 @@ */ public function register(Container $container) { + $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Privacy')); $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Privacy')); $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Privacy')); $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Privacy')); diff --git a/administrator/components/com_privacy/src/Controller/CookieController.php b/administrator/components/com_privacy/src/Controller/CookieController.php new file mode 100644 index 0000000000..f5e5a4e733 --- /dev/null +++ b/administrator/components/com_privacy/src/Controller/CookieController.php @@ -0,0 +1,26 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Controller; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\MVC\Controller\FormController; + +/** + * Controller for a cookie to edit. + * + * @since __DEPLOY_VERSION__ + */ +class CookieController extends FormController +{ +} diff --git a/administrator/components/com_privacy/src/Controller/CookieconsentController.php b/administrator/components/com_privacy/src/Controller/CookieconsentController.php new file mode 100644 index 0000000000..539827328a --- /dev/null +++ b/administrator/components/com_privacy/src/Controller/CookieconsentController.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Controller; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\MVC\Controller\FormController; + +/** + * Controller for a consent to view. + * + * @since __DEPLOY_VERSION__ + */ +class ConsentController extends FormController +{ +} diff --git a/administrator/components/com_privacy/src/Controller/CookieconsentsController.php b/administrator/components/com_privacy/src/Controller/CookieconsentsController.php new file mode 100644 index 0000000000..eae030618c --- /dev/null +++ b/administrator/components/com_privacy/src/Controller/CookieconsentsController.php @@ -0,0 +1,40 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Controller; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\MVC\Controller\AdminController; + +/** + * Consents list controller class. + * + * @since __DEPLOY_VERSION__ + */ +class ConsentsController extends AdminController +{ + /** + * Proxy for getModel. + * + * @param string $name The name of the model. + * @param string $prefix The prefix for the PHP class name. + * @param array $config Array of configuration parameters. + * + * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel + * + * @since __DEPLOY_VERSION__ + */ + public function getModel($name = 'Consent', $prefix = 'Administrator', $config = ['ignore_request' => true]) + { + return parent::getModel($name, $prefix, $config); + } +} diff --git a/administrator/components/com_privacy/src/Controller/CookiesController.php b/administrator/components/com_privacy/src/Controller/CookiesController.php new file mode 100644 index 0000000000..0c3f6fb0d1 --- /dev/null +++ b/administrator/components/com_privacy/src/Controller/CookiesController.php @@ -0,0 +1,41 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Controller; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\MVC\Controller\AdminController; + +/** + * Cookies list controller class. + * + * @since __DEPLOY_VERSION__ + */ +class CookiesController extends AdminController +{ + /** + * Proxy for getModel. + * + * @param string $name The name of the model. + * @param string $prefix The prefix for the PHP class name. + * @param array $config Array of configuration parameters. + * + * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel + * + * @since __DEPLOY_VERSION__ + */ + public function getModel($name = 'Cookie', $prefix = 'Administrator', $config = ['ignore_request' => true]) + { + return parent::getModel($name, $prefix, $config); + } +} diff --git a/administrator/components/com_privacy/src/Controller/ScriptController.php b/administrator/components/com_privacy/src/Controller/ScriptController.php new file mode 100644 index 0000000000..0f8771a1a6 --- /dev/null +++ b/administrator/components/com_privacy/src/Controller/ScriptController.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Controller; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\MVC\Controller\FormController; + +/** + * Controller for a script to edit. + * + * @since __DEPLOY_VERSION__ + */ +class ScriptController extends FormController +{ +} diff --git a/administrator/components/com_privacy/src/Controller/ScriptsController.php b/administrator/components/com_privacy/src/Controller/ScriptsController.php new file mode 100644 index 0000000000..d85698e04a --- /dev/null +++ b/administrator/components/com_privacy/src/Controller/ScriptsController.php @@ -0,0 +1,40 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Controller; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\MVC\Controller\AdminController; + +/** + * Scripts list controller class. + * + * @since __DEPLOY_VERSION__ + */ +class ScriptsController extends AdminController +{ + /** + * Proxy for getModel. + * + * @param string $name The name of the model. + * @param string $prefix The prefix for the PHP class name. + * @param array $config Array of configuration parameters. + * + * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel + * + * @since __DEPLOY_VERSION__ + */ + public function getModel($name = 'Script', $prefix = 'Administrator', $config = ['ignore_request' => true]) + { + return parent::getModel($name, $prefix, $config); + } +} diff --git a/administrator/components/com_privacy/src/Model/CookieModel.php b/administrator/components/com_privacy/src/Model/CookieModel.php new file mode 100644 index 0000000000..91d82b0c9f --- /dev/null +++ b/administrator/components/com_privacy/src/Model/CookieModel.php @@ -0,0 +1,175 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Factory; +use Joomla\CMS\Form\Form; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Model\AdminModel; +use Joomla\CMS\Table\Table; + +/** + * Cookie Model for a cookie to edit. + * + * @since __DEPLOY_VERSION__ + */ +class CookieModel extends AdminModel +{ + /** + * The prefix to use with controller messages. + * + * @var string + * @since __DEPLOY_VERSION__ + */ + protected $text_prefix = 'COM_PRIVACY'; + + /** + * 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 + * + * @since __DEPLOY_VERSION__ + * + * @throws \Exception + */ + public function getTable($name = '', $prefix = '', $options = []) + { + $name = 'cookie'; + $prefix = 'Table'; + + if ($table = $this->_createTable($name, $prefix, $options)) { + return $table; + } + + throw new \Exception(Text::sprintf('JLIB_APPLICATION_ERROR_TABLE_NAME_NOT_SUPPORTED', $name), 0); + } + + /** + * Method to get the record form. + * + * @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 __DEPLOY_VERSION__ + */ + public function getForm($data = [], $loadData = true) + { + $form = $this->loadForm('com_privacy.cookie', 'cookie', ['control' => 'jform', 'load_data' => $loadData]); + + if (empty($form)) { + return false; + } + + // Modify the form based on access controls. + if (!$this->canEditState((object) $data)) { + // Disable fields for display. + $form->setFieldAttribute('ordering', 'disabled', 'true'); + $form->setFieldAttribute('published', 'disabled', 'true'); + + // Disable fields while saving. + // The controller has already verified this is a record you can edit. + $form->setFieldAttribute('ordering', 'filter', 'unset'); + $form->setFieldAttribute('published', 'filter', 'unset'); + } + + // Don't allow to change the created_by user if not allowed to access com_users. + if (!Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_users')) { + $form->setFieldAttribute('created_by', 'filter', 'unset'); + } + + return $form; + } + + /** + * Method to get the data that should be injected in the form. + * + * @return mixed The data for the form. + * + * @since __DEPLOY_VERSION__ + */ + protected function loadFormData() + { + // Check the session for previously entered form data. + $app = Factory::getApplication(); + $data = $app->getUserState('com_privacy.edit.cookie.data', []); + + if (empty($data)) { + $data = $this->getItem(); + } + + $this->preprocessData('com_privacy.cookie', $data); + + return $data; + } + + /** + * Allows preprocessing of the Form object. + * + * @param Form $form The form object + * @param array $data The data to be merged into the form object + * @param string $group The plugin group to be executed + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function preprocessForm(Form $form, $data, $group = 'content') + { + parent::preprocessForm($form, $data, $group); + } + + /** + * Prepare and sanitise the table prior to saving. + * + * @param CookieTable $table The Table object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function prepareTable($table) + { + $date = Factory::getDate()->toSql(); + + $table->generateAlias(); + + if (empty($table->id)) { + // Set the values + $table->created = $date; + + // Set ordering to the last item if not set + if (empty($table->ordering)) { + $db = $this->getDatabase(); + $query = $db->getQuery(true) + ->select('MAX(ordering)') + ->from($db->quoteName('#__privacy_cookies')); + $db->setQuery($query); + $max = $db->loadResult(); + + $table->ordering = $max + 1; + } + } else { + // Set the values + $table->modified = $date; + $table->modified_by = Factory::getApplication()->getIdentity()->id; + } + } +} diff --git a/administrator/components/com_privacy/src/Model/CookieconsentModel.php b/administrator/components/com_privacy/src/Model/CookieconsentModel.php new file mode 100644 index 0000000000..f7f4856237 --- /dev/null +++ b/administrator/components/com_privacy/src/Model/CookieconsentModel.php @@ -0,0 +1,101 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\Factory; +use Joomla\CMS\Form\Form; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Model\AdminModel; +use Joomla\CMS\Table\Table; + +/** + * Consent Model for a consent to view. + * + * @since __DEPLOY_VERSION__ + */ + +class CookieconsentModel extends AdminModel +{ + /** + * The prefix to use with controller messages. + * + * @var string + * @since __DEPLOY_VERSION__ + */ + protected $text_prefix = 'COM_PRIVACY'; + + /** + * 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 + * + * @since __DEPLOY_VERSION__ + * @throws \Exception + */ + public function getTable($name = '', $prefix = '', $options = []) + { + $name = 'consent'; + $prefix = 'Table'; + if ($table = $this->_createTable($name, $prefix, $options)) { + return $table; + } + + throw new \Exception(Text::sprintf('JLIB_APPLICATION_ERROR_TABLE_NAME_NOT_SUPPORTED', $name), 0); + } + + /** + * Method to get the record form. + * + * @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 __DEPLOY_VERSION__ + */ + public function getForm($data = [], $loadData = true) + { + // Get the form. + $form = $this->loadForm('com_privacy.consent', 'consent', ['control' => 'jform', 'load_data' => $loadData]); + if (empty($form)) { + return false; + } + + return $form; + } + + /** + * Method to get the data that should be injected in the form. + * + * @return mixed The data for the form. + * + * @since __DEPLOY_VERSION__ + */ + protected function loadFormData() + { + // Check the session for previously entered form data. + $app = Factory::getApplication(); + $data = $app->getUserState('com_privacy.edit.cookie.data', []); + if (empty($data)) { + $data = $this->getItem(); + } + + $this->preprocessData('com_privacy.cookie', $data); + return $data; + } +} diff --git a/administrator/components/com_privacy/src/Model/CookieconsentsModel.php b/administrator/components/com_privacy/src/Model/CookieconsentsModel.php new file mode 100644 index 0000000000..352e661b23 --- /dev/null +++ b/administrator/components/com_privacy/src/Model/CookieconsentsModel.php @@ -0,0 +1,130 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\MVC\Model\ListModel; + +/** + * Methods supporting a list of consents records. + * + * @since __DEPLOY_VERSION__ + */ +class CookieconsentsModel extends ListModel +{ + /** + * Constructor. + * + * @param array $config An optional associative array of configuration settings. + * + * @since __DEPLOY_VERSION__ + * @see \Joomla\CMS\MVC\Controller\BaseController + */ + public function __construct($config = []) + { + if (empty($config['filter_fields'])) { + $config['filter_fields'] = [ + 'id', 'a.id', + 'uuid', 'a.uuid', + 'ccuuid', 'a.ccuuid', + 'consent_opt_in', 'a.consent_opt_in', + 'consent_opt_out', 'a.consent_opt_out', + 'consent_date', 'a.consent_date', + 'user_agent', 'a.user_agent', + 'url', 'a.url', + ]; + } + + parent::__construct($config); + } + + /** + * 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 __DEPLOY_VERSION__ + */ + protected function populateState($ordering = 'a.id', $direction = 'desc') + { + $search = $this->getUserStateFromRequest($this->context . 'filter.search', 'filter_search'); + $this->setState('filter.search', $search); + + // List state information. + parent::populateState($ordering, $direction); + } + + /** + * 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 A store id. + * + * @since __DEPLOY_VERSION__ + */ + protected function getStoreId($id = '') + { + // Compile the store id. + $id .= ':' . $this->getState('filter.search'); + + return parent::getStoreId($id); + } + + /** + * Build an SQL query to load the list data. + * + * @return \Joomla\Database\DatabaseQuery + * + * @since __DEPLOY_VERSION__ + */ + protected function getListQuery() + { + $db = $this->getDatabase(); + $query = $db->getQuery(true); + + $query->select( + $this->getState( + 'list.select', + 'a.id, a.uuid, a.ccuuid, a.consent_opt_in, a.consent_opt_out, a.consent_date, a.user_agent, a.url' + ) + ); + $query->from($db->quoteName('#__privacy_cookie_consents', 'a')); + + // Filter by search in ccuuid. + $search = $this->getState('filter.search'); + + if (!empty($search)) { + $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); + $query->where(['a.ccuuid LIKE ' . $search, 'a.uuid LIKE ' . $search], 'OR'); + } + + // Add the list ordering clause. + $orderCol = $this->state->get('list.ordering', 'a.id'); + $orderDirn = $this->state->get('list.direction', 'DESC'); + + $query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn)); + + return $query; + } +} diff --git a/administrator/components/com_privacy/src/Model/CookiesModel.php b/administrator/components/com_privacy/src/Model/CookiesModel.php new file mode 100644 index 0000000000..b783c902da --- /dev/null +++ b/administrator/components/com_privacy/src/Model/CookiesModel.php @@ -0,0 +1,181 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\MVC\Model\ListModel; +use Joomla\CMS\Table\Table; +use Joomla\Database\ParameterType; +use Joomla\Utilities\ArrayHelper; + +/** + * Methods supporting a list of cookies records. + * + * @since __DEPLOY_VERSION__ + */ +class CookiesModel extends ListModel +{ + /** + * Constructor. + * + * @param array $config An optional associative array of configuration settings. + * + * @since __DEPLOY_VERSION__ + * @see \Joomla\CMS\MVC\Controller\BaseController + */ + public function __construct($config = []) + { + if (empty($config['filter_fields'])) { + $config['filter_fields'] = [ + 'id', 'a.id', + 'title', 'a.title', + 'alias', 'a.alias', + 'cookie_name', 'a.cookie_name', + 'cookie_desc', 'a.cookie_desc', + 'exp_period', 'a.exp_period', + 'exp_value', 'a.exp_value', + 'catid', 'a.catid', 'category_id', 'category_title', + 'published', 'a.published', + 'ordering', 'a.ordering', + 'created', 'a.created_on', + 'created_by', 'a.created_by', + 'modified', 'a.modified_on', + 'modified_by', 'a.modified_by', + ]; + } + + parent::__construct($config); + } + + /** + * 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 __DEPLOY_VERSION__ + */ + protected function populateState($ordering = 'a.ordering', $direction = 'asc') + { + $search = $this->getUserStateFromRequest($this->context . 'filter.search', 'filter_search'); + $this->setState('filter.search', $search); + + // List state information. + parent::populateState($ordering, $direction); + } + + /** + * 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 A store id. + * + * @since __DEPLOY_VERSION__ + */ + protected function getStoreId($id = '') + { + // Compile the store id. + $id .= ':' . $this->getState('filter.search'); + $id .= ':' . $this->getState('filter.published'); + $id .= ':' . serialize($this->getState('filter.category_id')); + + return parent::getStoreId($id); + } + + /** + * Build an SQL query to load the list data. + * + * @return \Joomla\Database\DatabaseQuery + * + * @since __DEPLOY_VERSION__ + */ + protected function getListQuery() + { + $db = $this->getDatabase(); + $query = $db->getQuery(true); + + $query->select( + $this->getState( + 'list.select', + 'a.id, a.title, a.alias, a.cookie_name, a.cookie_desc, a.exp_period, ' . + 'a.exp_value, a.catid, a.published, a.ordering, a.created, a.created_by' + ) + ); + $query->from($db->quoteName('#__privacy_cookies', 'a')); + + // Join over the categories. + $query->select($db->quoteName('c.title', 'category_title')) + ->join( + 'LEFT', + $db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid') + ); + + // Filter by categories + $categoryId = $this->getState('filter.category_id', []); + + if (!is_array($categoryId)) { + $categoryId = $categoryId ? [$categoryId] : []; + } + + if (count($categoryId)) { + $categoryId = ArrayHelper::toInteger($categoryId); + $categoryTable = Table::getInstance('Category', 'JTable'); + $subCatItemsWhere = []; + + foreach ($categoryId as $filter_catid) { + $categoryTable->load($filter_catid); + $subCatItemsWhere[] = '(' . + 'c.lft >= ' . (int) $categoryTable->lft . ' AND ' . + 'c.rgt <= ' . (int) $categoryTable->rgt . ')'; + } + + $query->where('(' . implode(' OR ', $subCatItemsWhere) . ')'); + } + + // Filter by published state + $published = (string) $this->getState('filter.published'); + + if (is_numeric($published)) { + $query->where($db->quoteName('a.published') . ' = :published'); + $query->bind(':published', $published, ParameterType::INTEGER); + } elseif ($published === '') { + $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)'); + } + + // Filter by search in title. + $search = $this->getState('filter.search'); + + if (!empty($search)) { + $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); + $query->where('(a.title LIKE ' . $search . ')'); + } + + // Add the list ordering clause. + $orderCol = $this->state->get('list.ordering', 'a.ordering'); + $orderDirn = $this->state->get('list.direction', 'ASC'); + + $query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn)); + + return $query; + } +} diff --git a/administrator/components/com_privacy/src/Model/ScriptModel.php b/administrator/components/com_privacy/src/Model/ScriptModel.php new file mode 100644 index 0000000000..db1653cae4 --- /dev/null +++ b/administrator/components/com_privacy/src/Model/ScriptModel.php @@ -0,0 +1,228 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Factory; +use Joomla\CMS\Form\Form; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Model\AdminModel; +use Joomla\CMS\Table\Table; +use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper; + +/** + * Script Model for a script to edit. + * + * @since __DEPLOY_VERSION__ + */ + +class ScriptModel extends AdminModel +{ + /** + * The prefix to use with controller messages. + * + * @var string + * @since __DEPLOY_VERSION__ + */ + protected $text_prefix = 'COM_PRIVACY'; + + /** + * 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 + * + * @since __DEPLOY_VERSION__ + * + * @throws \Exception + */ + public function getTable($name = '', $prefix = '', $options = []) + { + $name = 'script'; + $prefix = 'Table'; + + if ($table = $this->_createTable($name, $prefix, $options)) { + return $table; + } + + throw new \Exception(Text::sprintf('JLIB_APPLICATION_ERROR_TABLE_NAME_NOT_SUPPORTED', $name), 0); + } + + /** + * Method to get the record form. + * + * @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 __DEPLOY_VERSION__ + */ + public function getForm($data = [], $loadData = true) + { + // Get the form. + $form = $this->loadForm('com_privacy.script', 'script', ['control' => 'jform', 'load_data' => $loadData]); + + if (empty($form)) { + return false; + } + + // Modify the form based on access controls. + if (!$this->canEditState((object) $data)) { + // Disable fields for display. + $form->setFieldAttribute('ordering', 'disabled', 'true'); + $form->setFieldAttribute('published', 'disabled', 'true'); + + // Disable fields while saving. + // The controller has already verified this is a record you can edit. + $form->setFieldAttribute('ordering', 'filter', 'unset'); + $form->setFieldAttribute('published', 'filter', 'unset'); + } + + return $form; + } + + /** + * Method to get the data that should be injected in the form. + * + * @return mixed The data for the form. + * + * @since __DEPLOY_VERSION__ + */ + protected function loadFormData() + { + // Check the session for previously entered form data. + $app = Factory::getApplication(); + $data = $app->getUserState('com_privacy.edit.script.data', []); + + if (empty($data)) { + $data = $this->getItem(); + } + + $this->preprocessData('com_privacy.script', $data); + + return $data; + } + + /** + * Allows preprocessing of the Form object. + * + * @param Form $form The form object + * @param array $data The data to be merged into the form object + * @param string $group The plugin group to be executed + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function preprocessForm(Form $form, $data, $group = 'content') + { + if ($this->canCreateCategory()) { + $form->setFieldAttribute('catid', 'allowAdd', 'true'); + + // Add a prefix for categories created on the fly. + $form->setFieldAttribute('catid', 'customPrefix', '#new#'); + } + + parent::preprocessForm($form, $data, $group); + } + + /** + * Prepare and sanitise the table prior to saving. + * + * @param ScriptTable $table The Table object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function prepareTable($table) + { + $table->generateAlias(); + + if (empty($table->ordering)) { + $db = $this->getDatabase(); + $query = $db->getQuery(true) + ->select('MAX(ordering)') + ->from($db->quoteName('#__privacy_scripts')); + $db->setQuery($query); + $max = $db->loadResult(); + + $table->ordering = $max + 1; + } + } + + /** + * Method to save the form data. + * + * @param array $data The form data. + * + * @return boolean True on success. + * + * @since __DEPLOY_VERSION__ + */ + public function save($data) + { + // Create new category, if needed. + $createCategory = true; + + // If category ID is provided, check if it's valid. + if (is_numeric($data['catid']) && $data['catid']) { + $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_privacy'); + } + + // Save New Category + if ($createCategory && $this->canCreateCategory()) { + $category = [ + // Remove #new# prefix, if exists. + 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'], + 'parent_id' => 1, + 'extension' => 'com_privacy', + 'language' => $data['language'], + 'published' => 1, + ]; + + /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */ + $categoryModel = Factory::getApplication()->bootComponent('com_categories') + ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]); + + // Create new category. + if (!$categoryModel->save($category)) { + $this->setError($categoryModel->getError()); + + return false; + } + + // Get the new category ID. + $data['catid'] = $categoryModel->getState('category.id'); + } + + return parent::save($data); + } + + /** + * Is the user allowed to create an on the fly category? + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + private function canCreateCategory() + { + return Factory::getUser()->authorise('core.create', 'com_privacy'); + } +} diff --git a/administrator/components/com_privacy/src/Model/ScriptsModel.php b/administrator/components/com_privacy/src/Model/ScriptsModel.php new file mode 100644 index 0000000000..e08fabff7f --- /dev/null +++ b/administrator/components/com_privacy/src/Model/ScriptsModel.php @@ -0,0 +1,176 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Model; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\MVC\Model\ListModel; +use Joomla\CMS\Table\Table; +use Joomla\Database\ParameterType; +use Joomla\Utilities\ArrayHelper; + +/** + * Methods supporting a list of scripts records. + * + * @since __DEPLOY_VERSION__ + */ +class ScriptsModel extends ListModel +{ + /** + * Constructor. + * + * @param array $config An optional associative array of configuration settings. + * + * @since __DEPLOY_VERSION__ + * @see \Joomla\CMS\MVC\Controller\BaseController + */ + public function __construct($config = []) + { + if (empty($config['filter_fields'])) { + $config['filter_fields'] = [ + 'id', 'a.id', + 'title', 'a.title', + 'alias', 'a.alias', + 'position', 'a.position', + 'type', 'a.type', + 'code', 'a.code', + 'catid', 'a.catid', 'category_id', 'category_title', + 'published', 'a.published', + 'ordering', 'a.ordering', + ]; + } + + parent::__construct($config); + } + + /** + * 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 __DEPLOY_VERSION__ + */ + protected function populateState($ordering = 'a.ordering', $direction = 'asc') + { + $search = $this->getUserStateFromRequest($this->context . 'filter.search', 'filter_search'); + $this->setState('filter.search', $search); + + // List state information. + parent::populateState($ordering, $direction); + } + + /** + * 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 A store id. + * + * @since __DEPLOY_VERSION__ + */ + protected function getStoreId($id = '') + { + // Compile the store id. + $id .= ':' . $this->getState('filter.search'); + $id .= ':' . $this->getState('filter.published'); + $id .= ':' . serialize($this->getState('filter.category_id')); + + return parent::getStoreId($id); + } + + /** + * Build an SQL query to load the list data. + * + * @return \Joomla\Database\DatabaseQuery + * + * @since __DEPLOY_VERSION__ + */ + protected function getListQuery() + { + $db = $this->getDatabase(); + $query = $db->getQuery(true); + + $query->select( + $this->getState( + 'list.select', + 'a.id, a.title, a.alias, a.position, a.type, a.code, ' . + 'a.catid, a.published, a.ordering' + ) + ); + $query->from($db->quoteName('#__privacy_scripts', 'a')); + + // Join over the categories. + $query->select($db->quoteName('c.title', 'category_title')) + ->join( + 'LEFT', + $db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid') + ); + + // Filter by categories + $categoryId = $this->getState('filter.category_id', []); + + if (!is_array($categoryId)) { + $categoryId = $categoryId ? [$categoryId] : []; + } + + if (count($categoryId)) { + $categoryId = ArrayHelper::toInteger($categoryId); + $categoryTable = Table::getInstance('Category', 'JTable'); + $subCatItemsWhere = []; + + foreach ($categoryId as $filter_catid) { + $categoryTable->load($filter_catid); + $subCatItemsWhere[] = '(' . + 'c.lft >= ' . (int) $categoryTable->lft . ' AND ' . + 'c.rgt <= ' . (int) $categoryTable->rgt . ')'; + } + + $query->where('(' . implode(' OR ', $subCatItemsWhere) . ')'); + } + + // Filter by published state + $published = (string) $this->getState('filter.published'); + + if (is_numeric($published)) { + $query->where($db->quoteName('a.published') . ' = :published'); + $query->bind(':published', $published, ParameterType::INTEGER); + } elseif ($published === '') { + $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)'); + } + + // Filter by search in title. + $search = $this->getState('filter.search'); + + if (!empty($search)) { + $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); + $query->where('(a.title LIKE ' . $search . ')'); + } + + // Add the list ordering clause. + $orderCol = $this->state->get('list.ordering', 'a.ordering'); + $orderDirn = $this->state->get('list.direction', 'ASC'); + + $query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn)); + + return $query; + } +} diff --git a/administrator/components/com_privacy/src/Table/CookieTable.php b/administrator/components/com_privacy/src/Table/CookieTable.php new file mode 100644 index 0000000000..53bfb6fbdc --- /dev/null +++ b/administrator/components/com_privacy/src/Table/CookieTable.php @@ -0,0 +1,156 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Table; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Application\ApplicationHelper; +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Table\Table; +use Joomla\Database\DatabaseDriver; + +/** + * Cookie Table class. + * + * @since __DEPLOY_VERSION__ + */ +class CookieTable extends Table +{ + /** + * Indicates that columns fully support the NULL value in the database + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + protected $_supportNullValue = true; + + /** + * Constructor + * + * @param DatabaseDriver $db Database connector object + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(DatabaseDriver $db) + { + parent::__construct('#__privacy_cookies', 'id', $db); + } + + /** + * Stores cookies. + * + * @param boolean $updateNulls True to update fields even if they are null. + * + * @return boolean True on success, false on failure. + * + * @since __DEPLOY_VERSION__ + */ + public function store($updateNulls = true) + { + $date = Factory::getDate()->toSql(); + $userId = Factory::getApplication()->getIdentity()->id; + + // Set created date if not set. + if (!(int) $this->created) { + $this->created = $date; + } + + if ($this->id) { + $this->modified_by = $userId; + $this->modified = $date; + } else { + if (empty($this->created_by)) { + $this->created_by = $userId; + } + + if (!(int) $this->modified) { + $this->modified = $date; + } + + if (empty($this->modified_by)) { + $this->modified_by = $userId; + } + } + + // Verify that the alias is unique + $table = Table::getInstance('CookieTable', __NAMESPACE__ . '\\', ['dbo' => $this->getDbo()]); + + if ($table->load(['alias' => $this->alias, 'catid' => $this->catid]) && ($table->id != $this->id || $this->id == 0)) { + $this->setError(Text::_('COM_PRIVACY_ERROR_UNIQUE_ALIAS')); + + return false; + } + + return parent::store($updateNulls); + } + + /** + * Overloaded check function + * + * @return boolean True on success, false on failure + * + * @see Table::check + * @since __DEPLOY_VERSION__ + */ + public function check() + { + try { + parent::check(); + } catch (\Exception $e) { + $this->setError($e->getMessage()); + + return false; + } + + // Check for valid title + if (trim($this->title) == '') { + $this->setError(Text::_('COM_PRIVACY_WARNING_PROVIDE_VALID_TITLE')); + + return false; + } + + // Generate a valid alias + $this->generateAlias(); + + // Check for a valid category. + if (!$this->catid = (int) $this->catid) { + $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED')); + + return false; + } + + return true; + } + + /** + * Generate a valid alias from title / date. + * Remains public to be able to check for duplicated alias before saving + * + * @return string + */ + public function generateAlias() + { + if (empty($this->alias)) { + $this->alias = $this->title; + } + + $this->alias = ApplicationHelper::stringURLSafe($this->alias); + + if (trim(str_replace('-', '', $this->alias)) == '') { + $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); + } + + return $this->alias; + } +} diff --git a/administrator/components/com_privacy/src/Table/CookieconsentTable.php b/administrator/components/com_privacy/src/Table/CookieconsentTable.php new file mode 100644 index 0000000000..10e49d9dc1 --- /dev/null +++ b/administrator/components/com_privacy/src/Table/CookieconsentTable.php @@ -0,0 +1,81 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Table; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Table\Table; +use Joomla\Database\DatabaseDriver; + +/** + * Consent Table class. + * + * @since __DEPLOY_VERSION__ + */ +class ConsentTable extends Table +{ + /** + * Indicates that columns fully support the NULL value in the database + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + protected $_supportNullValue = true; + + /** + * Constructor + * + * @param DatabaseDriver $db Database connector object + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(DatabaseDriver $db) + { + parent::__construct('#__privacy_cookie_consents', 'id', $db); + } + + /** + * Stores consents. + * + * @param boolean $updateNulls True to update fields even if they are null. + * + * @return boolean True on success, false on failure. + * + * @since __DEPLOY_VERSION__ + */ + public function store($updateNulls = true) + { + return parent::store($updateNulls); + } + + /** + * Overloaded check function + * + * @return boolean True on success, false on failure + * + * @see Table::check + * @since __DEPLOY_VERSION__ + */ + public function check() + { + try { + parent::check(); + } catch (\Exception $e) { + $this->setError($e->getMessage()); + + return false; + } + + return true; + } +} diff --git a/administrator/components/com_privacy/src/Table/ScriptTable.php b/administrator/components/com_privacy/src/Table/ScriptTable.php new file mode 100644 index 0000000000..fbe7de71f1 --- /dev/null +++ b/administrator/components/com_privacy/src/Table/ScriptTable.php @@ -0,0 +1,131 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\Table; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Application\ApplicationHelper; +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Table\Table; +use Joomla\Database\DatabaseDriver; + +/** + * Script Table class. + * + * @since __DEPLOY_VERSION__ + */ +class ScriptTable extends Table +{ + /** + * Indicates that columns fully support the NULL value in the database + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + protected $_supportNullValue = true; + + /** + * Constructor + * + * @param DatabaseDriver $db Database connector object + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(DatabaseDriver $db) + { + parent::__construct('#__privacy_scripts', 'id', $db); + } + + /** + * Stores scripts. + * + * @param boolean $updateNulls True to update fields even if they are null. + * + * @return boolean True on success, false on failure. + * + * @since __DEPLOY_VERSION__ + */ + public function store($updateNulls = true) + { + // Verify that the alias is unique + $table = Table::getInstance('ScriptTable', __NAMESPACE__ . '\\', ['dbo' => $this->getDbo()]); + + if ($table->load(['alias' => $this->alias, 'catid' => $this->catid]) && ($table->id != $this->id || $this->id == 0)) { + $this->setError(Text::_('COM_PRIVACY_ERROR_UNIQUE_ALIAS')); + + return false; + } + + return parent::store($updateNulls); + } + + /** + * Overloaded check function + * + * @return boolean True on success, false on failure + * + * @see Table::check + * @since __DEPLOY_VERSION__ + */ + public function check() + { + try { + parent::check(); + } catch (\Exception $e) { + $this->setError($e->getMessage()); + + return false; + } + + // Check for valid title + if (trim($this->title) == '') { + $this->setError(Text::_('COM_PRIVACY_WARNING_PROVIDE_VALID_TITLE')); + + return false; + } + + // Generate a valid alias + $this->generateAlias(); + + // Check for a valid category. + if (!$this->catid = (int) $this->catid) { + $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED')); + + return false; + } + + return true; + } + + /** + * Generate a valid alias from title / date. + * Remains public to be able to check for duplicated alias before saving + * + * @return string + */ + public function generateAlias() + { + if (empty($this->alias)) { + $this->alias = $this->title; + } + + $this->alias = ApplicationHelper::stringURLSafe($this->alias); + + if (trim(str_replace('-', '', $this->alias)) == '') { + $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); + } + + return $this->alias; + } +} diff --git a/administrator/components/com_privacy/src/View/Cookie/HtmlView.php b/administrator/components/com_privacy/src/View/Cookie/HtmlView.php new file mode 100644 index 0000000000..98c1554f60 --- /dev/null +++ b/administrator/components/com_privacy/src/View/Cookie/HtmlView.php @@ -0,0 +1,117 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\View\Cookie; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Factory; +use Joomla\CMS\Helper\ContentHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\GenericDataException; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\Toolbar; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * View to edit a cookie. + * + * @since __DEPLOY_VERSION__ + */ +class HtmlView extends BaseHtmlView +{ + /** + * The \JForm object + * + * @var \JForm + * @since __DEPLOY_VERSION__ + */ + protected $form; + + /** + * The active item + * + * @var object + * @since __DEPLOY_VERSION__ + */ + protected $item; + + /** + * The model state + * + * @var object + * @since __DEPLOY_VERSION__ + */ + protected $state; + + /** + * The actions the user is authorised to perform + * + * @var \JObject + * @since __DEPLOY_VERSION__ + */ + protected $canDo; + + /** + * Display the view. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return void + * + * @throws \GenericDataException + * @since __DEPLOY_VERSION__ + */ + public function display($tpl = null) + { + $this->form = $this->get('Form'); + $this->item = $this->get('Item'); + $this->state = $this->get('State'); + + 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 __DEPLOY_VERSION__ + */ + protected function addToolbar() + { + Factory::getApplication()->input->set('hidemainmenu', true); + + $canDo = ContentHelper::getActions('com_privacy'); + $isNew = ($this->item->id == 0); + + // Get the toolbar object instance + $toolbar = Toolbar::getInstance(); + + ToolbarHelper::title($isNew ? Text::_('COM_PRIVACY_COOKIE_NEW') : Text::_('COM_PRIVACY_COOKIE_EDIT'), 'cookie-bite'); + + if ($canDo->get('core.create')) { + $toolbar->apply('cookie.apply'); + $toolbar->save('cookie.save'); + } + + $toolbar->cancel('cookie.cancel'); + + ToolbarHelper::help('JHELP_COMPONENTS_COOKIEMANAGER_COOKIES_EDIT'); + } +} diff --git a/administrator/components/com_privacy/src/View/Cookieconsent/HtmlView.php b/administrator/components/com_privacy/src/View/Cookieconsent/HtmlView.php new file mode 100644 index 0000000000..22abb2115d --- /dev/null +++ b/administrator/components/com_privacy/src/View/Cookieconsent/HtmlView.php @@ -0,0 +1,108 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\View\Cookieconsent; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\GenericDataException; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\Toolbar; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * View to view a consent. + * + * @since __DEPLOY_VERSION__ + */ +class HtmlView extends BaseHtmlView +{ + /** + * The \JForm object + * + * @var \JForm + * @since __DEPLOY_VERSION__ + */ + protected $form; + + /** + * The active item + * + * @var object + * @since __DEPLOY_VERSION__ + */ + protected $item; + + /** + * The model state + * + * @var object + * @since __DEPLOY_VERSION__ + */ + protected $state; + + /** + * The actions the user is authorised to perform + * + * @var \JObject + * @since __DEPLOY_VERSION__ + */ + protected $canDo; + + /** + * Display the view. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return void + * + * @throws \GenericDataException + * @since __DEPLOY_VERSION__ + */ + public function display($tpl = null) + { + $this->form = $this->get('Form'); + $this->item = $this->get('Item'); + $this->state = $this->get('State'); + + 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 __DEPLOY_VERSION__ + */ + protected function addToolbar() + { + Factory::getApplication()->input->set('hidemainmenu', true); + + // Get the toolbar object instance + $toolbar = Toolbar::getInstance(); + + ToolbarHelper::title(Text::_('COM_PRIVACY_REVIEW_COOKIECONSENT'), 'lock'); + + $toolbar->cancel('consent.cancel'); + + ToolbarHelper::help('JHELP_COMPONENTS_PRIVACY_COOKIECONSENTS_EDIT'); + } +} diff --git a/administrator/components/com_privacy/src/View/Cookieconsents/HtmlView.php b/administrator/components/com_privacy/src/View/Cookieconsents/HtmlView.php new file mode 100644 index 0000000000..6a925e3678 --- /dev/null +++ b/administrator/components/com_privacy/src/View/Cookieconsents/HtmlView.php @@ -0,0 +1,133 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\View\Cookieconsents; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\Factory; +use Joomla\CMS\Helper\ContentHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\GenericDataException; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\Toolbar; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * View class for a list of consents. + * + * @since __DEPLOY_VERSION__ + */ +class HtmlView extends BaseHtmlView +{ + /** + * An array of items + * + * @var array + * @since __DEPLOY_VERSION__ + */ + protected $items; + + /** + * The pagination object + * + * @var \JPagination + * @since __DEPLOY_VERSION__ + */ + protected $pagination; + + /** + * The model state + * + * @var \JObject + * @since __DEPLOY_VERSION__ + */ + protected $state; + + /** + * Form object for search filters + * + * @var \JForm + * @since __DEPLOY_VERSION__ + */ + public $filterForm; + + /** + * The active search filters + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public $activeFilters; + + /** + * Is this view an Empty State + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + private $isEmptyState = false; + + /** + * Display the view. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return void + * + * @throws \Exception + * @since __DEPLOY_VERSION__ + */ + public function display($tpl = null) + { + $this->items = $this->get('Items'); + $this->pagination = $this->get('Pagination'); + $this->state = $this->get('State'); + $this->filterForm = $this->get('FilterForm'); + $this->activeFilters = $this->get('ActiveFilters'); + 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 __DEPLOY_VERSION__ + */ + protected function addToolbar() + { + $user = Factory::getApplication()->getIdentity(); + $canDo = ContentHelper::getActions('com_privacy'); + // Get the toolbar object instance + $toolbar = Toolbar::getInstance(); + ToolbarHelper::title(Text::_('COM_PRIVACY_CONSENTS'), 'lock'); + if (!$this->isEmptyState && $canDo->get('core.delete')) { + ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'consents.delete'); + } + + if ($user->authorise('core.admin', 'com_privacy')) { + $toolbar->preferences('com_privacy'); + } + + $toolbar->help('JHELP_COMPONENTS_COOKIEMANAGER_CONSENTS'); + } +} diff --git a/administrator/components/com_privacy/src/View/Cookies/HtmlView.php b/administrator/components/com_privacy/src/View/Cookies/HtmlView.php new file mode 100644 index 0000000000..a55142da1f --- /dev/null +++ b/administrator/components/com_privacy/src/View/Cookies/HtmlView.php @@ -0,0 +1,156 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\View\Cookies; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\Factory; +use Joomla\CMS\Helper\ContentHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\GenericDataException; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\Toolbar; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * View class for a list of cookies. + * + * @since __DEPLOY_VERSION__ + */ +class HtmlView extends BaseHtmlView +{ + /** + * An array of items + * + * @var array + * @since __DEPLOY_VERSION__ + */ + protected $items; + + /** + * The pagination object + * + * @var \JPagination + * @since __DEPLOY_VERSION__ + */ + protected $pagination; + + /** + * The model state + * + * @var \JObject + * @since __DEPLOY_VERSION__ + */ + protected $state; + + /** + * Form object for search filters + * + * @var \JForm + * @since __DEPLOY_VERSION__ + */ + public $filterForm; + + /** + * The active search filters + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public $activeFilters; + + /** + * Is this view an Empty State + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + private $isEmptyState = false; + + /** + * Display the view. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return void + * + * @throws \Exception + * @since __DEPLOY_VERSION__ + */ + public function display($tpl = null) + { + $this->items = $this->get('Items'); + $this->pagination = $this->get('Pagination'); + $this->state = $this->get('State'); + $this->filterForm = $this->get('FilterForm'); + $this->activeFilters = $this->get('ActiveFilters'); + 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 __DEPLOY_VERSION__ + */ + protected function addToolbar() + { + $user = Factory::getApplication()->getIdentity(); + $canDo = ContentHelper::getActions('com_privacy', 'category', $this->state->get('filter.category_id')); + // Get the toolbar object instance + $toolbar = Toolbar::getInstance(); + ToolbarHelper::title(Text::_('COM_PRIVACY_COOKIES'), 'cookie'); + if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_privacy', 'core.create')) > 0) { + $toolbar->addNew('cookie.add'); + } + + if (!$this->isEmptyState && ($canDo->get('core.edit.state'))) { + $dropdown = $toolbar->dropdownButton('status-group') + ->text('JTOOLBAR_CHANGE_STATUS') + ->toggleSplit(false) + ->icon('icon-ellipsis-h') + ->buttonClass('btn btn-action') + ->listCheck(true); + $childBar = $dropdown->getChildToolbar(); + $childBar->publish('cookies.publish')->listCheck(true); + $childBar->unpublish('cookies.unpublish')->listCheck(true); + $childBar->archive('cookies.archive')->listCheck(true); + if ($this->state->get('filter.published') != -2) { + $childBar->trash('cookies.trash')->listCheck(true); + } + } + + if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) { + $toolbar->delete('cookies.delete') + ->text('JTOOLBAR_EMPTY_TRASH') + ->message('JGLOBAL_CONFIRM_DELETE') + ->listCheck(true); + } + + if ($user->authorise('core.admin', 'com_privacy')) { + $toolbar->preferences('com_privacy'); + } + + $toolbar->help('JHELP_COMPONENTS_COOKIEMANAGER_COOKIES'); + } +} diff --git a/administrator/components/com_privacy/src/View/Script/HtmlView.php b/administrator/components/com_privacy/src/View/Script/HtmlView.php new file mode 100644 index 0000000000..5d312e4575 --- /dev/null +++ b/administrator/components/com_privacy/src/View/Script/HtmlView.php @@ -0,0 +1,109 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\View\Script; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\Factory; +use Joomla\CMS\Helper\ContentHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\GenericDataException; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\Toolbar; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * View to edit a script. + * + * @since __DEPLOY_VERSION__ + */ +class HtmlView extends BaseHtmlView +{ + /** + * The \JForm object + * + * @var \JForm + * @since __DEPLOY_VERSION__ + */ + protected $form; + + /** + * The active item + * + * @var object + * @since __DEPLOY_VERSION__ + */ + protected $item; + + /** + * The model state + * + * @var object + * @since __DEPLOY_VERSION__ + */ + protected $state; + + /** + * The actions the user is authorised to perform + * + * @var \JObject + * @since __DEPLOY_VERSION__ + */ + protected $canDo; + + /** + * Display the view. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return void + * + * @throws \Exception + * @since __DEPLOY_VERSION__ + */ + public function display($tpl = null) + { + $this->form = $this->get('Form'); + $this->item = $this->get('Item'); + $this->state = $this->get('State'); + 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 __DEPLOY_VERSION__ + */ + protected function addToolbar() + { + Factory::getApplication()->input->set('hidemainmenu', true); + $canDo = ContentHelper::getActions('com_privacy'); + $isNew = ($this->item->id == 0); + // Get the toolbar object instance + $toolbar = Toolbar::getInstance(); + ToolbarHelper::title($isNew ? Text::_('COM_PRIVACY_SCRIPT_NEW') : Text::_('COM_PRIVACY_SCRIPT_EDIT'), 'code'); + if ($canDo->get('core.create')) { + $toolbar->apply('script.apply'); + $toolbar->save('script.save'); + } + + $toolbar->cancel('script.cancel'); + ToolbarHelper::help('JHELP_COMPONENTS_COOKIEMANAGER_SCRIPTS_EDIT'); + } +} diff --git a/administrator/components/com_privacy/src/View/Scripts/HtmlView.php b/administrator/components/com_privacy/src/View/Scripts/HtmlView.php new file mode 100644 index 0000000000..5b0a460ea7 --- /dev/null +++ b/administrator/components/com_privacy/src/View/Scripts/HtmlView.php @@ -0,0 +1,156 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Privacy\Administrator\View\Scripts; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects +use Joomla\CMS\Factory; +use Joomla\CMS\Helper\ContentHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\GenericDataException; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\Toolbar; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * View class for a list of scripts. + * + * @since __DEPLOY_VERSION__ + */ +class HtmlView extends BaseHtmlView +{ + /** + * An array of items + * + * @var array + * @since __DEPLOY_VERSION__ + */ + protected $items; + + /** + * The pagination object + * + * @var \JPagination + * @since __DEPLOY_VERSION__ + */ + protected $pagination; + + /** + * The model state + * + * @var \JObject + * @since __DEPLOY_VERSION__ + */ + protected $state; + + /** + * Form object for search filters + * + * @var \JForm + * @since __DEPLOY_VERSION__ + */ + public $filterForm; + + /** + * The active search filters + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public $activeFilters; + + /** + * Is this view an Empty State + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + private $isEmptyState = false; + + /** + * Display the view. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return void + * + * @throws \Exception + * @since __DEPLOY_VERSION__ + */ + public function display($tpl = null) + { + $this->items = $this->get('Items'); + $this->pagination = $this->get('Pagination'); + $this->state = $this->get('State'); + $this->filterForm = $this->get('FilterForm'); + $this->activeFilters = $this->get('ActiveFilters'); + 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 __DEPLOY_VERSION__ + */ + protected function addToolbar() + { + $user = Factory::getApplication()->getIdentity(); + $canDo = ContentHelper::getActions('com_privacy', 'category', $this->state->get('filter.category_id')); + // Get the toolbar object instance + $toolbar = Toolbar::getInstance(); + ToolbarHelper::title(Text::_('COM_PRIVACY_SCRIPTS'), 'code'); + if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_privacy', 'core.create')) > 0) { + $toolbar->addNew('script.add'); + } + + if (!$this->isEmptyState && $canDo->get('core.edit.state')) { + $dropdown = $toolbar->dropdownButton('status-group') + ->text('JTOOLBAR_CHANGE_STATUS') + ->toggleSplit(false) + ->icon('icon-ellipsis-h') + ->buttonClass('btn btn-action') + ->listCheck(true); + $childBar = $dropdown->getChildToolbar(); + $childBar->publish('scripts.publish')->listCheck(true); + $childBar->unpublish('scripts.unpublish')->listCheck(true); + $childBar->archive('scripts.archive')->listCheck(true); + if ($this->state->get('filter.published') != -2) { + $childBar->trash('scripts.trash')->listCheck(true); + } + } + + if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) { + $toolbar->delete('scripts.delete') + ->text('JTOOLBAR_EMPTY_TRASH') + ->message('JGLOBAL_CONFIRM_DELETE') + ->listCheck(true); + } + + if ($user->authorise('core.admin', 'com_privacy')) { + $toolbar->preferences('com_privacy'); + } + + $toolbar->help('JHELP_COMPONENTS_COOKIEMANAGER_SCRIPTS'); + } +} diff --git a/administrator/components/com_privacy/tmpl/consents/default.php b/administrator/components/com_privacy/tmpl/consents/default.php index 6a6c6f0ade..7797f1f569 100644 --- a/administrator/components/com_privacy/tmpl/consents/default.php +++ b/administrator/components/com_privacy/tmpl/consents/default.php @@ -31,8 +31,8 @@ $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') + 0 => Text::_('COM_PRIVACY_CONSENTS_STATE_OBSOLETE'), + 1 => Text::_('COM_PRIVACY_CONSENTS_STATE_VALID'), ]; ?> diff --git a/administrator/components/com_privacy/tmpl/cookie/edit.php b/administrator/components/com_privacy/tmpl/cookie/edit.php new file mode 100644 index 0000000000..5da06fd47b --- /dev/null +++ b/administrator/components/com_privacy/tmpl/cookie/edit.php @@ -0,0 +1,69 @@ + + * @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\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +HTMLHelper::_('behavior.formvalidator'); +HTMLHelper::_('behavior.keepalive'); + +?> + + diff --git a/administrator/components/com_privacy/tmpl/cookieconsent/edit.php b/administrator/components/com_privacy/tmpl/cookieconsent/edit.php new file mode 100644 index 0000000000..fc3f98b4eb --- /dev/null +++ b/administrator/components/com_privacy/tmpl/cookieconsent/edit.php @@ -0,0 +1,43 @@ + + * @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; + +?> + + diff --git a/administrator/components/com_privacy/tmpl/cookieconsents/default.php b/administrator/components/com_privacy/tmpl/cookieconsents/default.php new file mode 100644 index 0000000000..4125d9657e --- /dev/null +++ b/administrator/components/com_privacy/tmpl/cookieconsents/default.php @@ -0,0 +1,110 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +HTMLHelper::_('behavior.multiselect'); + +$user = Factory::getApplication()->getIdentity(); +$userId = $user->get('id'); +$listOrder = $this->escape($this->state->get('list.id')); +$listDirn = $this->escape($this->state->get('list.direction')); +$saveOrder = ($listOrder == 'a.id' && strtolower($listDirn) == 'desc'); + +?> + +
+
+
+
+ $this]); ?> + items)) : ?> +
+ + + +
+ + + + + + + + + + + + + items); + foreach ($this->items as $i => $item) : + $canCreate = $user->authorise('core.create'); + $canEdit = $user->authorise('core.edit'); + $canEditOwn = $user->authorise('core.edit.own'); + $canChange = $user->authorise('core.edit.state'); + ?> + + + + + + + + +
+ , + , + +
+ + + + + + + +
+ id, false, 'cid', 'cb', $item->uuid); ?> + +
+ + + ccuuid; ?> + + + ccuuid; ?> + +
+ uuid; ?> +
+ consent_date, Text::_('DATE_FORMAT_LC4')); ?> + + id; ?> +
+ + + + pagination->getListFooter(); ?> + + + + +
+
+
+
diff --git a/administrator/components/com_privacy/tmpl/cookieconsents/emptystate.php b/administrator/components/com_privacy/tmpl/cookieconsents/emptystate.php new file mode 100644 index 0000000000..59efd8e608 --- /dev/null +++ b/administrator/components/com_privacy/tmpl/cookieconsents/emptystate.php @@ -0,0 +1,20 @@ + + * @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', + 'icon' => 'icon-lock', +]; +echo LayoutHelper::render('joomla.content.emptystate', $displayData); diff --git a/administrator/components/com_privacy/tmpl/cookies/default.php b/administrator/components/com_privacy/tmpl/cookies/default.php new file mode 100644 index 0000000000..3a9b41abee --- /dev/null +++ b/administrator/components/com_privacy/tmpl/cookies/default.php @@ -0,0 +1,134 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +HTMLHelper::_('behavior.multiselect'); + +$user = Factory::getApplication()->getIdentity(); +$userId = $user->get('id'); +$listOrder = $this->escape($this->state->get('list.ordering')); +$listDirn = $this->escape($this->state->get('list.direction')); +$saveOrder = ($listOrder == 'a.ordering' && strtolower($listDirn) == 'asc'); + +?> + +
+
+
+
+ $this]); ?> + items)) : ?> +
+ + + +
+ + + + + + + + + + + + + + + items); + foreach ($this->items as $i => $item) : + $canCreate = $user->authorise('core.create', 'com_privacy.category.' . $item->catid); + $canEdit = $user->authorise('core.edit', 'com_privacy.category.' . $item->catid); + $canEditOwn = $user->authorise('core.edit.own', 'com_privacy.category.' . $item->catid) && $item->created_by == $userId; + $canChange = $user->authorise('core.edit.state', 'com_privacy.category.' . $item->catid); + ?> + + + + + + + + + + +
+ , + , + +
+ + + + + + + + + + + +
+ id, false, 'cid', 'cb', $item->title); ?> + + + + + + + published, $i, 'cookies.', $canChange, 'cb'); ?> + +
+ + + escape($item->title); ?> + + + escape($item->title); ?> + + + escape($item->alias)); ?> + +
+
+ category_title; ?> + + id; ?> +
+ + + + pagination->getListFooter(); ?> + + + + +
+
+
+
diff --git a/administrator/components/com_privacy/tmpl/cookies/emptystate.php b/administrator/components/com_privacy/tmpl/cookies/emptystate.php new file mode 100644 index 0000000000..0d77e1bc92 --- /dev/null +++ b/administrator/components/com_privacy/tmpl/cookies/emptystate.php @@ -0,0 +1,29 @@ + + * @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_COOKIES', + 'formURL' => 'index.php?option=com_privacy', + 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage', + 'icon' => 'icon-cookie', +]; + +$user = Factory::getApplication()->getIdentity(); + +if ($user->authorise('core.create', 'com_privacy') || count($user->getAuthorisedCategories('com_privacy', 'core.create')) > 0) { + $displayData['createURL'] = 'index.php?option=com_privacy&task=cookie.add'; +} + +echo LayoutHelper::render('joomla.content.emptystate', $displayData); diff --git a/administrator/components/com_privacy/tmpl/requests/default.php b/administrator/components/com_privacy/tmpl/requests/default.php index 5c0252b2f3..b605e64024 100644 --- a/administrator/components/com_privacy/tmpl/requests/default.php +++ b/administrator/components/com_privacy/tmpl/requests/default.php @@ -118,7 +118,7 @@ - + pagination->getListFooter(); ?> diff --git a/administrator/components/com_privacy/tmpl/script/edit.php b/administrator/components/com_privacy/tmpl/script/edit.php new file mode 100644 index 0000000000..9a30827cd3 --- /dev/null +++ b/administrator/components/com_privacy/tmpl/script/edit.php @@ -0,0 +1,49 @@ + + * @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\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +HTMLHelper::_('behavior.formvalidator'); +HTMLHelper::_('behavior.keepalive'); + +?> + + diff --git a/administrator/components/com_privacy/tmpl/scripts/default.php b/administrator/components/com_privacy/tmpl/scripts/default.php new file mode 100644 index 0000000000..ba689a4e19 --- /dev/null +++ b/administrator/components/com_privacy/tmpl/scripts/default.php @@ -0,0 +1,162 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +HTMLHelper::_('behavior.multiselect'); + +$user = Factory::getApplication()->getIdentity(); +$userId = $user->get('id'); +$listOrder = $this->escape($this->state->get('list.ordering')); +$listDirn = $this->escape($this->state->get('list.direction')); +$saveOrder = ($listOrder == 'a.ordering' && strtolower($listDirn) == 'asc'); + +$positions = [ + '1' => Text::_('COM_PRIVACY_SCRIPT_POSITION_AFTER_BEGIN_HEAD'), + '2' => Text::_('COM_PRIVACY_SCRIPT_POSITION_BEFORE_END_HEAD'), + '3' => Text::_('COM_PRIVACY_SCRIPT_POSITION_AFTER_BEGIN_BODY'), + '4' => Text::_('COM_PRIVACY_SCRIPT_POSITION_BEFORE_END_BODY'), +]; + +$types = [ + '1' => Text::_('COM_PRIVACY_SCRIPT_TYPE_SCRIPT'), + '2' => Text::_('COM_PRIVACY_SCRIPT_TYPE_EXTERNAL_SCRIPT'), + '3' => Text::_('COM_PRIVACY_SCRIPT_TYPE_IFRAME'), + '4' => Text::_('COM_PRIVACY_SCRIPT_TYPE_EMBED'), + '5' => Text::_('COM_PRIVACY_SCRIPT_TYPE_OBJECT'), + '6' => Text::_('COM_PRIVACY_SCRIPT_TYPE_IMG'), + '7' => Text::_('COM_PRIVACY_SCRIPT_TYPE_LINK'), +]; +?> + +
+
+
+
+ $this]); ?> + items)) : ?> +
+ + + +
+ + + + + + + + + + + + + + + + + items); + foreach ($this->items as $i => $item) : + $canCreate = $user->authorise('core.create', 'com_privacy.category.' . $item->catid); + $canEdit = $user->authorise('core.edit', 'com_privacy.category.' . $item->catid); + $canEditOwn = $user->authorise('core.edit.own', 'com_privacy.category.' . $item->catid); + $canChange = $user->authorise('core.edit.state', 'com_privacy.category.' . $item->catid); + ?> + + + + + + + + + + + + +
+ , + , + +
+ + + + + + + + + + + + + + + +
+ id, false, 'cid', 'cb', $item->title); ?> + + + + + + + published, $i, 'scripts.', $canChange, 'cb'); ?> + +
+ + + escape($item->title); ?> + + + escape($item->title); ?> + + + escape($item->alias)); ?> + +
+
+ position]; ?> + + type]; ?> + + category_title; ?> + + id; ?> +
+ + + + pagination->getListFooter(); ?> + + + + +
+
+
+
diff --git a/administrator/components/com_privacy/tmpl/scripts/emptystate.php b/administrator/components/com_privacy/tmpl/scripts/emptystate.php new file mode 100644 index 0000000000..85a657fef7 --- /dev/null +++ b/administrator/components/com_privacy/tmpl/scripts/emptystate.php @@ -0,0 +1,29 @@ + + * @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_SCRIPTS', + 'formURL' => 'index.php?option=com_privacy&view=scripts', + 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage', + 'icon' => 'icon-code', +]; + +$user = Factory::getApplication()->getIdentity(); + +if ($user->authorise('core.create', 'com_privacy') || count($user->getAuthorisedCategories('com_privacy', 'core.create')) > 0) { + $displayData['createURL'] = 'index.php?option=com_privacy&task=script.add'; +} + +echo LayoutHelper::render('joomla.content.emptystate', $displayData); diff --git a/administrator/language/en-GB/com_privacy.ini b/administrator/language/en-GB/com_privacy.ini index 2c5f771c81..09f9129790 100644 --- a/administrator/language/en-GB/com_privacy.ini +++ b/administrator/language/en-GB/com_privacy.ini @@ -169,3 +169,155 @@ COM_PRIVACY_VIEW_REQUEST_ADD_REQUEST="Privacy: New Information Request" COM_PRIVACY_VIEW_REQUEST_SHOW_REQUEST="Privacy: Review Information Request" COM_PRIVACY_WARNING_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED="Information requests can't be created when sending mail is disabled." COM_PRIVACY_XML_DESCRIPTION="Component for managing privacy related actions." + +COM_PRIVACY_ACCEPT_ALL_COOKIES_BUTTON_TEXT="Accept all cookies" +COM_PRIVACY_CATEGORIES="Categories" +COM_PRIVACY_CATEGORY_COOKIES="Cookies" +COM_PRIVACY_CATEGORY_FIELD_MANDATORY_LABEL="Mandatory requirements" +COM_PRIVACY_CATEGORY_FIELD_MANDATORY_DESC="Cookies of a mandatory category must be accepted by users. Choose mandatory when the cookies are technically needed for this website." +COM_PRIVACY_CATEGORY_MANDATORY="Mandatory" +COM_PRIVACY_CATEGORY_OPTIONAL="Optional" +COM_PRIVACY_CONFIG_BUTTON_ROLE_ACCEPT_ALL="Accept all" +COM_PRIVACY_CONFIG_BUTTON_ROLE_ACCEPT_NECESSARY="Accept necessary" +COM_PRIVACY_CONFIG_BUTTON_ROLE_ACCEPT_SELECTED="Accept selected" +COM_PRIVACY_CONFIG_BUTTON_ROLE_SETTINGS="Settings" +COM_PRIVACY_CONFIG_CONSENT_EXPIRATION_LABEL="Consent Expiration Period (days)" +COM_PRIVACY_CONFIG_CONSENT_MODAL_LAYOUT_FIELD_LABEL="Consent Modal Layout" +COM_PRIVACY_CONFIG_CONSENT_MODAL_POSITION_X_FIELD_LABEL="Modal Position X Axis" +COM_PRIVACY_CONFIG_CONSENT_MODAL_POSITION_Y_FIELD_LABEL="Modal Position Y Axis" +COM_PRIVACY_CONFIG_CONSENT_MODAL_PRIMARY_BUTTON_ROLE_FIELD_LABEL="Primary button role" +COM_PRIVACY_CONFIG_CONSENT_MODAL_SECONDARY_BUTTON_ROLE_FIELD_LABEL="Secondary button role" +COM_PRIVACY_CONFIG_CONSENT_MODAL_SWAP_BUTTONS_FIELD_LABEL="Swap Buttons" +COM_PRIVACY_CONFIG_CONSENT_MODAL_TRANSITION_FIELD_LABEL="Modal Transition" +COM_PRIVACY_CONFIG_COOKIE_SAME_SITE_LABEL="SameSite attribute" +COM_PRIVACY_CONFIG_COOKIE_SAME_SITE_LAX="Lax" +COM_PRIVACY_CONFIG_COOKIE_SAME_SITE_STRICT="Strict" +COM_PRIVACY_CONFIG_COOKIE_SAME_SITE_NONE="None" +COM_PRIVACY_CONFIG_DELAY_LABEL="Delay" +COM_PRIVACY_CONFIG_FIELDSET_GENERAL_LABEL="General" +COM_PRIVACY_CONFIG_FIELDSET_CONSENT_MODAL_LABEL="Consent Modal" +COM_PRIVACY_CONFIG_FIELDSET_SETTINGS_MODAL_LABEL="Settings Modal" +COM_PRIVACY_CONFIG_FORCE_CONSENT_LABEL="Force Consent" +COM_PRIVACY_CONFIG_HIDE_FROM_BOTS_LABEL="Hide banner from bots" +COM_PRIVACY_CONFIG_LAYOUT_CLOUD="Cloud" +COM_PRIVACY_CONFIG_LAYOUT_BAR="Bar" +COM_PRIVACY_CONFIG_LAYOUT_BOX="Box" +COM_PRIVACY_CONFIG_MODE_LABEL="Selection Mode" +COM_PRIVACY_CONFIG_POLICY_LINK_LABEL="Cookie Policy Link" +COM_PRIVACY_CONFIG_POSITION_BOTTOM="Bottom" +COM_PRIVACY_CONFIG_POSITION_CENTER="Center" +COM_PRIVACY_CONFIG_POSITION_LEFT="Left" +COM_PRIVACY_CONFIG_POSITION_MIDDLE="Middle" +COM_PRIVACY_CONFIG_POSITION_RIGHT="Right" +COM_PRIVACY_CONFIG_POSITION_TOP="Top" +COM_PRIVACY_CONFIG_REMOVE_COOKIE_TABLES_LABEL="Remove cookie table" +COM_PRIVACY_CONFIG_SETTINGS_MODAL_LAYOUT_FIELD_LABEL="Modal Layout" +COM_PRIVACY_CONFIG_SETTINGS_MODAL_POSITION_FIELD_LABEL="Modal Position" +COM_PRIVACY_CONFIG_SETTINGS_MODAL_TRANSITION_FIELD_LABEL="Modal Transition" +COM_PRIVACY_CONFIG_TRANSITION_SLIDE="Slide" +COM_PRIVACY_CONFIG_TRANSITION_ZOOM="Zoom" +COM_PRIVACY_CONFIRM_MY_CHOICES_BUTTON_TEXT="Confirm my choices" +COM_PRIVACY_CONSENT_ID="Consent ID" +COM_PRIVACY_CONSENT_BUTTON_TEXT="Cookie consent" +COM_PRIVACY_CONSENTS="Cookie Manager: Consents" +COM_PRIVACY_COOKIECONSENTS_EMPTYSTATE_CONTENT="This screen displays all Cookie Consents that have been given." +COM_PRIVACY_COOKIECONSENTS_EMPTYSTATE_TITLE="No Consents have been given yet." +COM_PRIVACY_COOKIE_BANNER_DESCRIPTION="This website uses cookies to ensure that we give you the best experience on our website." +COM_PRIVACY_COOKIE_BANNER_TITLE="This website uses cookies" +COM_PRIVACY_COOKIE_DESC_HINT="It registers a unique ID by the provider that is used to generate statistical data on how the visitor uses the website." +COM_PRIVACY_COOKIE_DESC_LABEL="Enter a short description explaining the advantage for the user" +COM_PRIVACY_COOKIE_EDIT="Cookie Manager: Edit Cookie" +COM_PRIVACY_COOKIE_EXP_PERIOD_DAYS="Days" +COM_PRIVACY_COOKIE_EXP_PERIOD_FOREVER="Forever" +COM_PRIVACY_COOKIE_EXP_PERIOD_HOURS="Hours" +COM_PRIVACY_COOKIE_EXP_PERIOD_LABEL="Please choose the expiration period for the cookie" +COM_PRIVACY_COOKIE_EXP_PERIOD_MINUTES="Minutes" +COM_PRIVACY_COOKIE_EXP_PERIOD_MONTHS="Months" +COM_PRIVACY_COOKIE_EXP_PERIOD_SECONDS="Seconds" +COM_PRIVACY_COOKIE_EXP_PERIOD_SESSION="Session" +COM_PRIVACY_COOKIE_EXP_PERIOD_WEEKS="Weeks" +COM_PRIVACY_COOKIE_EXP_PERIOD_YEARS="Years" +COM_PRIVACY_COOKIE_EXP_VALUE_LABEL="Enter the expiration value (in integer)" +COM_PRIVACY_COOKIE_NAME_HINT="_gid" +COM_PRIVACY_COOKIE_NAME_LABEL="Enter the name under which the cookie is stored" +COM_PRIVACY_COOKIE_NEW="Cookie Manager: New Cookie" +COM_PRIVACY_COOKIES="Cookie Manager: Cookies" +COM_PRIVACY_COOKIES_EMPTYSTATE_BUTTON_ADD="Add cookies" +COM_PRIVACY_COOKIES_EMPTYSTATE_CONTENT="You can use this component to add and manage your cookies." +COM_PRIVACY_COOKIES_EMPTYSTATE_TITLE="No Cookies have been added yet." +COM_PRIVACY_ERROR_UNIQUE_ALIAS="Another Cookies list from this category has the same alias (remember it may be a trashed item)." +COM_PRIVACY_FIELD_CCUUID_LABEL="CCUUID" +COM_PRIVACY_FIELD_CODE_LABEL="Code" +COM_PRIVACY_FIELD_CONSENT_DATE_LABEL="Consent Date" +COM_PRIVACY_FIELD_CONSENT_OPT_IN_LABEL="Consent Opt-in" +COM_PRIVACY_FIELD_CONSENT_OPT_OUT_LABEL="Consent Opt-out" +COM_PRIVACY_FIELD_CREATED_LABEL="Created Date" +COM_PRIVACY_FIELD_POSITION_LABEL="Position" +COM_PRIVACY_FIELD_TYPE_LABEL="Type" +COM_PRIVACY_FIELD_URL_LABEL="URL" +COM_PRIVACY_FIELD_USER_AGENT_LABEL="User Agent" +COM_PRIVACY_FIELD_UUID_LABEL="UUID" +COM_PRIVACY_FILTER_SEARCH_CONSENTS_DESC="Search in CCUUID or UUID in consents list." +COM_PRIVACY_FILTER_SEARCH_CONSENTS_LABEL="Search consents list" +COM_PRIVACY_FILTER_SEARCH_COOKIES_DESC="Search in title in cookies list." +COM_PRIVACY_FILTER_SEARCH_COOKIES_LABEL="Search cookies list" +COM_PRIVACY_FILTER_SEARCH_SCRIPTS_DESC="Search in title in scripts list." +COM_PRIVACY_FILTER_SEARCH_SCRIPTS_LABEL="Search scripts list" +COM_PRIVACY_FIELD_TITLE_LABEL="Title" +COM_PRIVACY_FORM_TITLE_EDIT_COOKIE="Edit Cookie" +COM_PRIVACY_FORM_TITLE_EDIT_SCRIPT="Edit Script" +COM_PRIVACY_FORM_TITLE_NEW_COOKIE="New Cookie" +COM_PRIVACY_FORM_TITLE_NEW_SCRIPT="New Script" +COM_PRIVACY_MANAGE_CONSENT_PREFERENCES="Manage Consent Preferences" +COM_PRIVACY_MORE_DETAILS="More details" +COM_PRIVACY_N_ITEMS_ARCHIVED="%s Items Archived" +COM_PRIVACY_N_ITEMS_PUBLISHED="%s Items Published" +COM_PRIVACY_N_ITEMS_UNPUBLISHED="%s Items Unpublished" +COM_PRIVACY_N_ITEMS_TRASHED="%s Items Trashed" +COM_PRIVACY_N_ITEMS_DELETED="%s Items Deleted" +COM_PRIVACY_OPT_IN="Opt-In" +COM_PRIVACY_OPT_OUT="Opt-Out" +COM_PRIVACY_POLICY_LINK_NONE="None" +COM_PRIVACY_POSITION_ASC="Position ascending" +COM_PRIVACY_POSITION_DESC="Position descending" +COM_PRIVACY_POSITION_FIELD_BOTTOM="Bottom" +COM_PRIVACY_POSITION_FIELD_BOTTOM_LEFT="Bottom Left Corner" +COM_PRIVACY_POSITION_FIELD_BOTTOM_RIGHT="Bottom Right Corner" +COM_PRIVACY_POSITION_FIELD_CENTER="Center" +COM_PRIVACY_POSITION_FIELD_LABEL="Cookie Banner Position" +COM_PRIVACY_POSITION_FIELD_LEFT="Left" +COM_PRIVACY_POSITION_FIELD_RIGHT="Right" +COM_PRIVACY_POSITION_FIELD_TOP="Top" +COM_PRIVACY_POSITION_FIELD_TOP_LEFT="Top Left Corner" +COM_PRIVACY_POSITION_FIELD_TOP_RIGHT="Top Right Corner" +COM_PRIVACY_PREFERENCES_LESS_BUTTON_TEXT="Less" +COM_PRIVACY_PREFERENCES_MORE_BUTTON_TEXT="More" +COM_PRIVACY_PREFERENCES_TITLE="Cookie Settings" +COM_PRIVACY_PREFERENCES_DESCRIPTION="When you visit our website, it may store or retrieve information on your browser, mostly in the form of cookies. This information might be about you, your preferences or your device and is mostly used to make the site work as you expect it to." +COM_PRIVACY_PREVIEW_BUTTON_TEXT="Cookie Settings" +COM_PRIVACY_REVIEW_CONSENT="Cookie Manager: Review Consent" +COM_PRIVACY_REVOKE_BUTTON_TEXT="Decline all cookies" +COM_PRIVACY_SCRIPT_EDIT="Cookie Manager: Edit Script" +COM_PRIVACY_SCRIPT_NEW="Cookie Manager: New Script" +COM_PRIVACY_SCRIPT_POSITION_AFTER_BEGIN_BODY="After begin body" +COM_PRIVACY_SCRIPT_POSITION_AFTER_BEGIN_HEAD="After begin head" +COM_PRIVACY_SCRIPT_POSITION_BEFORE_END_BODY="Before end body" +COM_PRIVACY_SCRIPT_POSITION_BEFORE_END_HEAD="Before end head" +COM_PRIVACY_SCRIPT_TYPE_EMBED="Embed" +COM_PRIVACY_SCRIPT_TYPE_EXTERNAL_SCRIPT="External script" +COM_PRIVACY_SCRIPT_TYPE_IFRAME="Iframe" +COM_PRIVACY_SCRIPT_TYPE_IMG="Img" +COM_PRIVACY_SCRIPT_TYPE_LINK="Link" +COM_PRIVACY_SCRIPT_TYPE_OBJECT="Object" +COM_PRIVACY_SCRIPT_TYPE_SCRIPT="Script" +COM_PRIVACY_SCRIPTS="Cookie Manager: Scripts" +COM_PRIVACY_SCRIPTS_EMPTYSTATE_BUTTON_ADD="Add scripts" +COM_PRIVACY_SCRIPTS_EMPTYSTATE_CONTENT="You can use this component to add and manage your cookie scripts." +COM_PRIVACY_SCRIPTS_EMPTYSTATE_TITLE="No Scripts have been added yet." +COM_PRIVACY_TABLE_HEAD_COOKIENAME="Cookie Name" +COM_PRIVACY_TABLE_HEAD_DESCRIPTION="Description" +COM_PRIVACY_TABLE_HEAD_EXPIRATION="Expiration" +COM_PRIVACY_TYPE_ASC="Type ascending" +COM_PRIVACY_TYPE_DESC="Type descending" +COM_PRIVACY_VIEW_COOKIE_POLICY="View our cookie policy" +COM_PRIVACY_WARNING_PROVIDE_VALID_TITLE="Please provide a valid title." diff --git a/administrator/language/en-GB/en-GB.plg_system_cookiemanager.ini b/administrator/language/en-GB/en-GB.plg_system_cookiemanager.ini new file mode 100644 index 0000000000..15f0a9a20e --- /dev/null +++ b/administrator/language/en-GB/en-GB.plg_system_cookiemanager.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; (C) 2021 Open Source Matters, Inc. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_SYSTEM_COOKIEMANAGER="System - Cookie Manager" +PLG_SYSTEM_COOKIEMANAGER_XML_DESCRIPTION="This plugin lets you manage cookies." diff --git a/administrator/language/en-GB/en-GB.plg_system_cookiemanager.sys.ini b/administrator/language/en-GB/en-GB.plg_system_cookiemanager.sys.ini new file mode 100644 index 0000000000..15f0a9a20e --- /dev/null +++ b/administrator/language/en-GB/en-GB.plg_system_cookiemanager.sys.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; (C) 2021 Open Source Matters, Inc. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_SYSTEM_COOKIEMANAGER="System - Cookie Manager" +PLG_SYSTEM_COOKIEMANAGER_XML_DESCRIPTION="This plugin lets you manage cookies." diff --git a/administrator/language/en-GB/mod_menu.ini b/administrator/language/en-GB/mod_menu.ini index 4c207b1d27..8ec02dea34 100644 --- a/administrator/language/en-GB/mod_menu.ini +++ b/administrator/language/en-GB/mod_menu.ini @@ -20,9 +20,13 @@ MOD_MENU_COM_LANGUAGES_SUBMENU_OVERRIDES="Overrides" MOD_MENU_COM_MAILS_TEMPLATES="Mail Templates" MOD_MENU_COM_PRIVACY="Privacy" MOD_MENU_COM_PRIVACY_CAPABILITIES="Capabilities" +MOD_MENU_COM_PRIVACY_CATEGORIES="Service Categories" MOD_MENU_COM_PRIVACY_CONSENTS="Consents" +MOD_MENU_COM_PRIVACY_COOKIE_CONSENTS="Cookie Consents" +MOD_MENU_COM_PRIVACY_COOKIES="Cookies" MOD_MENU_COM_PRIVACY_DASHBOARD="Dashboard" MOD_MENU_COM_PRIVACY_REQUESTS="Requests" +MOD_MENU_COM_PRIVACY_SCRIPTS="Scripts" MOD_MENU_COM_TEMPLATES_SUBMENU_STYLES="Styles" MOD_MENU_COM_TEMPLATES_SUBMENU_TEMPLATES="Templates" MOD_MENU_COM_USERS="Users" diff --git a/administrator/language/en-GB/plg_system_privacyconsent.ini b/administrator/language/en-GB/plg_system_privacyconsent.ini index 30ef029455..1df112bc0c 100644 --- a/administrator/language/en-GB/plg_system_privacyconsent.ini +++ b/administrator/language/en-GB/plg_system_privacyconsent.ini @@ -4,6 +4,12 @@ ; Note : All ini files need to be saved as UTF-8 PLG_SYSTEM_PRIVACYCONSENT="System - Privacy Consent" +PLG_SYSTEM_PRIVACYCONSENT_BANNER_TITLE="Joomla uses cookies!" +PLG_SYSTEM_PRIVACYCONSENT_BANNER_DESC="Hi, this website uses essential cookies to ensure its proper operation and tracking cookies to understand how you interact with it. The latter will be set only after consent. " +PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_ACCEPT_ALL="Accept all" +PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_ACCEPT_SELECTED="Accept selected" +PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_REJECT_ALL="Reject all" +PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_SETTINGS="Settings" PLG_SYSTEM_PRIVACYCONSENT_BODY="

The user consented to storing their user information using the IP address %s

The user agent string of the user's browser was:
%s

This information was automatically recorded when the user submitted their details on the website and checked the confirm box

" PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_DESC="How often the check is performed." PLG_SYSTEM_PRIVACYCONSENT_CACHETIMEOUT_LABEL="Periodic check (days)" @@ -38,5 +44,14 @@ PLG_SYSTEM_PRIVACYCONSENT_REDIRECT_MESSAGE_DESC="Custom message to be displayed PLG_SYSTEM_PRIVACYCONSENT_REDIRECT_MESSAGE_LABEL="Redirect Message" PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_DESC="Number of days to send a reminder before the expiration of the privacy consent." PLG_SYSTEM_PRIVACYCONSENT_REMINDBEFORE_LABEL="Remind" +PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_TITLE="Cookie preferences" +PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_SAVE="Save settings" +PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_ACCEPT_ALL="Accept all" +PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_REJECT_ALL="Reject all" +PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_CLOSE="Close" PLG_SYSTEM_PRIVACYCONSENT_SUBJECT="Privacy Policy" +PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL1="Name" +PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL2="Domain" +PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL3="Expiration" +PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL4="Description" PLG_SYSTEM_PRIVACYCONSENT_XML_DESCRIPTION="Basic plugin to request user's consent to the site's privacy policy. Existing users who have not consented yet will be redirected on login to update their profile." diff --git a/build/build-modules-js/settings.json b/build/build-modules-js/settings.json index abb5dbe5cb..c0b6f526cf 100644 --- a/build/build-modules-js/settings.json +++ b/build/build-modules-js/settings.json @@ -797,6 +797,28 @@ ] } ] + }, + "vanilla-cookieconsent": { + "name": "cookieconsent", + "licenseFilename": "LICENSE", + "js" : { + "dist/cookieconsent.js": "js/cookieconsent.js" + }, + "css": { + "dist/cookieconsent.css": "css/cookieconsent.css" + }, + "provideAssets": [ + { + "name": "cookieconsent", + "type": "script", + "uri": "cookieconsent.js" + }, + { + "name": "cookieconsent", + "type": "style", + "uri": "cookieconsent.css" + } + ] } }, "errorPages": { diff --git a/build/media_source/plg_system_privacyconsent/js/privacyconsent.es6.js b/build/media_source/plg_system_privacyconsent/js/privacyconsent.es6.js new file mode 100644 index 0000000000..7e2f5e7e42 --- /dev/null +++ b/build/media_source/plg_system_privacyconsent/js/privacyconsent.es6.js @@ -0,0 +1,175 @@ +/** + * @copyright (C) 2023 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +((document, Joomla) => { + 'use strict'; + + if (!Joomla) { + throw new Error('Joomla API is not properly initialised'); + } + /* global initCookieConsent */ + const cc = initCookieConsent(); + + const config = Joomla.getOptions('plg_system_privacyconsent.config'); + console.log({ config }); + const cookies = Joomla.getOptions('plg_system_privacyconsent.cookies'); + console.log({ cookies }); + const categories = Joomla.getOptions('plg_system_privacyconsent.categories'); + console.log({ categories }); + const scripts = Joomla.getOptions('plg_system_privacyconsent.scripts'); + console.log({ scripts }); + + const primaryButtonLabel = config.consent_modal.primary_button_role === 'accept_all' + ? Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_ACCEPT_ALL') + : Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_ACCEPT_SELECTED'); + const secondaryButtonLabel = config.consent_modal.secondary_button_role === 'accept_necessary' + ? Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_REJECT_ALL') + : Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_SETTINGS'); + + const getBlocks = () => { + const blocks = []; + categories.forEach((category) => { + const params = JSON.parse(category.params); + const mandatory = params.mandatory && params.mandatory === '1'; + const block = { + title: category.title, + description: category.description, + toggle: { + value: category.alias, + enabled: mandatory, + readonly: mandatory, + }, + }; + const categoryCookies = cookies.filter((cookie) => cookie.alias === category.alias); + if (categoryCookies.length) { + block.cookie_table = []; + categoryCookies.forEach((cookie) => { + block.cookie_table.push({ + col1: cookie.cookie_name, + col2: cookie.domain, + col3: cookie.exp_period, + col4: cookie.cookie_desc, + }); + }); + } + blocks.push(block); + }); + + return blocks; + }; + + const saveConsent = (cookie, preferences) => { + const consentDetails = { + uuid: cookie.consent_uuid, + url: window.location.href, + consent_opt_in: preferences.accepted_categories, + consent_opt_out: cookie.categories, + }; + Joomla.request({ + url: `index.php?option=com_ajax&plugin=privacyconsent&group=system&format=json&data=${JSON.stringify(consentDetails)}`, + method: 'POST', + onSuccess: (response) => { + const result = JSON.parse(response); + console.log({ result }); + }, + onError(xhr) { + Joomla.renderMessages({ error: [xhr] }, '#system-message-container'); + }, + }); + }; + + const showSettingButton = () => { + const openSettingsButton = document.createElement('button'); + openSettingsButton.innerText = Joomla.Text._('COM_PRIVACY_PREVIEW_BUTTON_TEXT'); + openSettingsButton.id = 'open-consent-banner'; + openSettingsButton.type = 'button'; + // openBannerButton.dataset.cc = 'c-settings'; // Not working + openSettingsButton.onclick = () => { + cc.showSettings(); + }; + openSettingsButton.className = 'preview btn btn-info'; + document.body.appendChild(openSettingsButton); + }; + + document.addEventListener('DOMContentLoaded', () => { + cc.run({ + current_lang: 'en', + autoclear_cookies: true, + page_scripts: true, + cookie_expiration: config.expiration, + cookie_name: 'jcookie', + mode: config.mode, + delay: config.delay, + force_consent: config.force_consent === '1', + hide_from_bots: config.hide_from_bots === '1', + remove_cookie_tables: config.remove_cookie_tables === '1', + cookie_same_site: config.cookie_same_site, + gui_options: { + consent_modal: { + layout: config.consent_modal.layout, + position: `${config.consent_modal.position_y} ${config.consent_modal.position_x}`, + transition: config.consent_modal.transition, + swap_buttons: config.consent_modal.swap_buttons === '1', + }, + settings_modal: { + layout: config.settings_modal.layout, + position: config.settings_modal.position, + transition: config.settings_modal.transition, + }, + }, + onFirstAction: (userPreferences, cookie) => { + // triggered only once after user gave first permission + console.log('onFirstAction:', { userPreferences }, { cookie }); + saveConsent(cookie, userPreferences); + }, + onChange: (cookie, changedPreferences) => { + // triggered on every setting change + console.log('onChange: ', { cookie }, { changedPreferences }); + saveConsent(cookie, changedPreferences); + }, + onAccept: (cookie) => { + // triggered on every page after user triggered 'firstAction' event + console.log('onAccept: ', { cookie }); + showSettingButton(); + }, + languages: { + en: { + consent_modal: { + title: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_BANNER_TITLE'), + description: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_BANNER_DESC'), + primary_btn: { + text: primaryButtonLabel, + role: config.consent_modal.primary_button_role, + }, + secondary_btn: { + text: secondaryButtonLabel, + role: config.consent_modal.secondary_button_role, + }, + }, + settings_modal: { + title: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_TITLE'), + save_settings_btn: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_SAVE'), + accept_all_btn: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_ACCEPT_ALL'), + reject_all_btn: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_REJECT_ALL'), + close_btn_label: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_CLOSE'), + cookie_table_headers: [ + { col1: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL1') }, + { col2: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL2') }, + { col3: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL3') }, + { col4: Joomla.Text._('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL4') }, + ], + blocks: [ + ...getBlocks(), + { + title: 'More information', + description: 'For any queries in relation to our policy on cookies and your choices, please contact us.', + }, + ], + }, + }, + }, + }); + }); +})(document, Joomla); diff --git a/build/media_source/system/scss/_icomoon.scss b/build/media_source/system/scss/_icomoon.scss index a17e6afd40..ecc7124b23 100644 --- a/build/media_source/system/scss/_icomoon.scss +++ b/build/media_source/system/scss/_icomoon.scss @@ -177,6 +177,8 @@ $jicons: ( connection : $fa-var-wifi, contract-2 : $fa-var-shopping-basket, contract : $fa-var-compress, + cookie : $fa-var-cookie, + cookie-bite : $fa-var-cookie-bite, copy : $fa-var-copy, credit-2 : $fa-var-credit-card, credit : $fa-var-credit-card, diff --git a/installation/sql/mysql/base.sql b/installation/sql/mysql/base.sql index fb0e213ec8..d332b2fb09 100644 --- a/installation/sql/mysql/base.sql +++ b/installation/sql/mysql/base.sql @@ -113,7 +113,9 @@ INSERT INTO `#__assets` (`id`, `parent_id`, `lft`, `rgt`, `level`, `name`, `titl (93, 1, 171, 172, 1, 'com_fields', 'com_fields', '{}'), (94, 1, 173, 174, 1, 'com_workflow', 'com_workflow', '{}'), (95, 1, 175, 176, 1, 'com_guidedtours', 'com_guidedtours', '{}'), -(96, 18, 130, 131, 2, 'com_modules.module.109', 'Guided Tours', '{}'); +(96, 18, 130, 131, 2, 'com_modules.module.109', 'Guided Tours', '{}'), +(97, 65, 132, 133, 2, 'com_privacy.category.8', 'Uncategorised', '{}'), +(98, 65, 134, 135, 2, 'com_privacy.category.9', 'Mandatory', '{mandatory: "1"}'); -- -------------------------------------------------------- @@ -494,7 +496,7 @@ CREATE TABLE IF NOT EXISTS `#__menu` ( -- INSERT INTO `#__menu` (`id`, `menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`, `publish_up`, `publish_down`) VALUES -(1, '', 'Menu_Item_Root', 'root', '', '', '', '', 1, 0, 0, 0, 0, 0, '', 0, '', 0, 43, 0, '*', 0, NULL, NULL); +(1, '', 'Menu_Item_Root', 'root', '', '', '', '', 1, 0, 0, 0, 0, 0, '', 0, '', 0, 53, 0, '*', 0, NULL, NULL); INSERT INTO `#__menu` (`id`, `menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`, `publish_up`, `publish_down`) SELECT 2, 'main', 'com_banners', 'Banners', '', 'Banners', 'index.php?option=com_banners', 'component', 1, 1, 1, `extension_id`, 0, 0, 'class:bookmark', 0, '', 1, 10, 0, '*', 1, NULL, NULL FROM `#__extensions` WHERE `name` = 'com_banners'; INSERT INTO `#__menu` (`id`, `menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`, `publish_up`, `publish_down`) @@ -536,7 +538,7 @@ SELECT 20, 'main', 'com_finder_filters', 'Smart-Search-Filters', '', 'Smart Sear INSERT INTO `#__menu` (`id`, `menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`, `publish_up`, `publish_down`) SELECT 21, 'main', 'com_finder_searches', 'Smart-Search-Searches', '', 'Smart Search/Searches', 'index.php?option=com_finder&view=searches', 'component', 1, 13, 2, `extension_id`, 0, 0, 'class:finder-searches', 0, '', 36, 37, 0, '*', 1, NULL, NULL FROM `#__extensions` WHERE `name` = 'com_finder'; INSERT INTO `#__menu` (`id`, `menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`, `publish_up`, `publish_down`) -SELECT 101, 'mainmenu', 'Home', 'home', '', 'home', 'index.php?option=com_content&view=featured', 'component', 1, 1, 1, `extension_id`, 0, 1, '', 0, '{"featured_categories":[""],"layout_type":"blog","blog_class_leading":"","blog_class":"","num_leading_articles":"1","num_intro_articles":"3","num_links":"0","link_intro_image":"","orderby_pri":"","orderby_sec":"front","order_date":"","show_pagination":"2","show_pagination_results":"1","show_title":"","link_titles":"","show_intro":"","info_block_position":"","info_block_show_title":"","show_category":"","link_category":"","show_parent_category":"","link_parent_category":"","show_associations":"","show_author":"","link_author":"","show_create_date":"","show_modify_date":"","show_publish_date":"","show_item_navigation":"","show_vote":"","show_readmore":"","show_readmore_title":"","show_hits":"","show_tags":"","show_noauth":"","show_feed_link":"1","feed_summary":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_image_css":"","menu_text":1,"menu_show":1,"page_title":"","show_page_heading":"1","page_heading":"","pageclass_sfx":"","menu-meta_description":"","robots":""}', 41, 42, 1, '*', 0, NULL, NULL FROM `#__extensions` WHERE `name` = 'com_content'; +SELECT 101, 'mainmenu', 'Home', 'home', '', 'home', 'index.php?option=com_content&view=featured', 'component', 1, 1, 1, `extension_id`, 0, 1, '', 0, '{"featured_categories":[""],"layout_type":"blog","blog_class_leading":"","blog_class":"","num_leading_articles":"1","num_intro_articles":"3","num_links":"0","link_intro_image":"","orderby_pri":"","orderby_sec":"front","order_date":"","show_pagination":"2","show_pagination_results":"1","show_title":"","link_titles":"","show_intro":"","info_block_position":"","info_block_show_title":"","show_category":"","link_category":"","show_parent_category":"","link_parent_category":"","show_associations":"","show_author":"","link_author":"","show_create_date":"","show_modify_date":"","show_publish_date":"","show_item_navigation":"","show_vote":"","show_readmore":"","show_readmore_title":"","show_hits":"","show_tags":"","show_noauth":"","show_feed_link":"1","feed_summary":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_image_css":"","menu_text":1,"menu_show":1,"page_title":"","show_page_heading":"1","page_heading":"","pageclass_sfx":"","menu-meta_description":"","robots":""}', 51, 52, 1, '*', 0, NULL, NULL FROM `#__extensions` WHERE `name` = 'com_content'; -- -------------------------------------------------------- diff --git a/installation/sql/mysql/extensions.sql b/installation/sql/mysql/extensions.sql index bd13932b11..deef7d89b4 100644 --- a/installation/sql/mysql/extensions.sql +++ b/installation/sql/mysql/extensions.sql @@ -757,6 +757,69 @@ CREATE TABLE IF NOT EXISTS `#__privacy_consents` ( -- -------------------------------------------------------- +-- +-- Table structure for table `#__privacy_cookies` +-- + +CREATE TABLE IF NOT EXISTS `#__privacy_cookies` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `title` varchar(255) NOT NULL, + `alias` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `cookie_name` varchar(255) NOT NULL, + `cookie_desc` varchar(255) NOT NULL, + `exp_period` varchar(20) NOT NULL, + `exp_value` int NOT NULL DEFAULT 0, + `catid` int NOT NULL DEFAULT 0, + `published` tinyint NOT NULL DEFAULT 1, + `ordering` int NOT NULL DEFAULT 0, + `created` datetime NOT NULL, + `created_by` int unsigned NOT NULL DEFAULT 0, + `modified` datetime NOT NULL, + `modified_by` int unsigned NOT NULL DEFAULT 0, + KEY `idx_state` (`published`), + KEY `idx_catid` (`catid`), + KEY `idx_createdby` (`created_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `#__privacy_scripts` +-- + +CREATE TABLE IF NOT EXISTS `#__privacy_scripts` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `title` varchar(255) NOT NULL, + `alias` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `position` int NOT NULL DEFAULT 4, + `type` int NOT NULL DEFAULT 1, + `code` text NOT NULL, + `catid` int NOT NULL DEFAULT 0, + `published` tinyint NOT NULL DEFAULT 1, + `ordering` int NOT NULL DEFAULT 0, + KEY `idx_state` (`published`), + KEY `idx_catid` (`catid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `#__privacy_cookie_consents` +-- + +CREATE TABLE IF NOT EXISTS `#__privacy_cookie_consents` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `uuid` varchar(32) NOT NULL, + `ccuuid` varchar(64) NOT NULL, + `consent_opt_in` varchar(255) NOT NULL, + `consent_opt_out` varchar(255) NOT NULL, + `consent_date` datetime NOT NULL, + `user_agent` varchar(150) NOT NULL, + `url` varchar(100) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + -- -- Table structure for table `#__redirect_links` -- @@ -830,7 +893,8 @@ INSERT INTO `#__action_logs_extensions` (`id`, `extension`) VALUES (16, 'com_templates'), (17, 'com_users'), (18, 'com_checkin'), -(19, 'com_scheduler'); +(19, 'com_scheduler'), +(20, 'com_privacy'); -- -------------------------------------------------------- @@ -869,7 +933,8 @@ INSERT INTO `#__action_log_config` (`id`, `type_title`, `type_alias`, `id_holder (17, 'access_level', 'com_users.level', 'id' , 'title', '#__viewlevels', 'PLG_ACTIONLOG_JOOMLA'), (18, 'banner_client', 'com_banners.client', 'id', 'name', '#__banner_clients', 'PLG_ACTIONLOG_JOOMLA'), (19, 'application_config', 'com_config.application', '', 'name', '', 'PLG_ACTIONLOG_JOOMLA'), -(20, 'task', 'com_scheduler.task', 'id', 'title', '#__scheduler_tasks', 'PLG_ACTIONLOG_JOOMLA'); +(20, 'task', 'com_scheduler.task', 'id', 'title', '#__scheduler_tasks', 'PLG_ACTIONLOG_JOOMLA'), +(21, 'privacy_cookie', 'com_privacy.cookie', 'id', 'name', '#__privacy_cookies', 'PLG_ACTIONLOG_JOOMLA'); -- -------------------------------------------------------- diff --git a/installation/sql/mysql/supports.sql b/installation/sql/mysql/supports.sql index 034682c5a2..f384f6d5e4 100644 --- a/installation/sql/mysql/supports.sql +++ b/installation/sql/mysql/supports.sql @@ -69,7 +69,9 @@ INSERT INTO `#__categories` (`id`, `asset_id`, `parent_id`, `lft`, `rgt`, `level (3, 28, 1, 3, 4, 1, 'uncategorised', 'com_banners', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1), (4, 29, 1, 5, 6, 1, 'uncategorised', 'com_contact', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1), (5, 30, 1, 7, 8, 1, 'uncategorised', 'com_newsfeeds', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1), -(7, 32, 1, 9, 10, 1, 'uncategorised', 'com_users', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1); +(7, 32, 1, 9, 10, 1, 'uncategorised', 'com_users', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1), +(8, 92, 1, 11, 12, 1, 'uncategorised', 'com_privacy', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":"","mandatory":"0"}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1), +(9, 93, 1, 13, 14, 1, 'mandatory', 'com_privacy', 'Mandatory', 'mandatory', '', '', 1, 1, '{"category_layout":"","image":"","mandatory":"1"}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP(), 42, CURRENT_TIMESTAMP(), 0, '*', 1); -- -------------------------------------------------------- diff --git a/installation/sql/postgresql/base.sql b/installation/sql/postgresql/base.sql index 4938db2808..ff79ecf453 100644 --- a/installation/sql/postgresql/base.sql +++ b/installation/sql/postgresql/base.sql @@ -119,7 +119,9 @@ INSERT INTO "#__assets" ("id", "parent_id", "lft", "rgt", "level", "name", "titl (93, 1, 171, 172, 1, 'com_fields', 'com_fields', '{}'), (94, 1, 173, 174, 1, 'com_workflow', 'com_workflow', '{}'), (95, 1, 175, 176, 1, 'com_guidedtours', 'com_guidedtours', '{}'), -(96, 18, 130, 131, 2, 'com_modules.module.109', 'Guided Tours', '{}'); +(96, 18, 130, 131, 2, 'com_modules.module.109', 'Guided Tours', '{}'), +(97, 65, 132, 133, 2, 'com_privacy.category.8', 'Uncategorised', '{}'), +(98, 65, 134, 135, 2, 'com_privacy.category.9', 'Mandatory', '{mandatory: "1"}'); SELECT setval('#__assets_id_seq', 97, false); @@ -519,7 +521,7 @@ COMMENT ON COLUMN "#__menu"."home" IS 'Indicates if this menu item is the home o -- INSERT INTO "#__menu" ("id", "menutype", "title", "alias", "note", "path", "link", "type", "published", "parent_id", "level", "component_id", "browserNav", "access", "img", "template_style_id", "params", "lft", "rgt", "home", "language", "client_id", "publish_up", "publish_down") VALUES -(1, '', 'Menu_Item_Root', 'root', '', '', '', '', 1, 0, 0, 0, 0, 0, '', 0, '', 0, 43, 0, '*', 0, NULL, NULL); +(1, '', 'Menu_Item_Root', 'root', '', '', '', '', 1, 0, 0, 0, 0, 0, '', 0, '', 0, 53, 0, '*', 0, NULL, NULL); INSERT INTO "#__menu" ("id", "menutype", "title", "alias", "note", "path", "link", "type", "published", "parent_id", "level", "component_id", "browserNav", "access", "img", "template_style_id", "params", "lft", "rgt", "home", "language", "client_id", "publish_up", "publish_down") SELECT 2, 'main', 'com_banners', 'Banners', '', 'Banners', 'index.php?option=com_banners', 'component', 1, 1, 1, "extension_id", 0, 0, 'class:bookmark', 0, '', 1, 10, 0, '*', 1, NULL, NULL FROM "#__extensions" WHERE "name" = 'com_banners'; INSERT INTO "#__menu" ("id", "menutype", "title", "alias", "note", "path", "link", "type", "published", "parent_id", "level", "component_id", "browserNav", "access", "img", "template_style_id", "params", "lft", "rgt", "home", "language", "client_id", "publish_up", "publish_down") @@ -561,7 +563,7 @@ SELECT 20, 'main', 'com_finder_filters', 'Smart-Search-Filters', '', 'Smart Sear INSERT INTO "#__menu" ("id", "menutype", "title", "alias", "note", "path", "link", "type", "published", "parent_id", "level", "component_id", "browserNav", "access", "img", "template_style_id", "params", "lft", "rgt", "home", "language", "client_id", "publish_up", "publish_down") SELECT 21, 'main', 'com_finder_searches', 'Smart-Search-Searches', '', 'Smart Search/Searches', 'index.php?option=com_finder&view=searches', 'component', 1, 13, 2, "extension_id", 0, 0, 'class:finder-searches', 0, '', 36, 37, 0, '*', 1, NULL, NULL FROM "#__extensions" WHERE "name" = 'com_finder'; INSERT INTO "#__menu" ("id", "menutype", "title", "alias", "note", "path", "link", "type", "published", "parent_id", "level", "component_id", "browserNav", "access", "img", "template_style_id", "params", "lft", "rgt", "home", "language", "client_id", "publish_up", "publish_down") -SELECT 101, 'mainmenu', 'Home', 'home', '', 'home', 'index.php?option=com_content&view=featured', 'component', 1, 1, 1, "extension_id", 0, 1, '', 0, '{"featured_categories":[""],"layout_type":"blog","blog_class_leading":"","blog_class":"","num_leading_articles":"1","num_intro_articles":"3","num_links":"0","link_intro_image":"","orderby_pri":"","orderby_sec":"front","order_date":"","show_pagination":"2","show_pagination_results":"1","show_title":"","link_titles":"","show_intro":"","info_block_position":"","info_block_show_title":"","show_category":"","link_category":"","show_parent_category":"","link_parent_category":"","show_associations":"","show_author":"","link_author":"","show_create_date":"","show_modify_date":"","show_publish_date":"","show_item_navigation":"","show_vote":"","show_readmore":"","show_readmore_title":"","show_hits":"","show_tags":"","show_noauth":"","show_feed_link":"1","feed_summary":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_image_css":"","menu_text":1,"menu_show":1,"page_title":"","show_page_heading":"1","page_heading":"","pageclass_sfx":"","menu-meta_description":"","robots":""}', 41, 42, 1, '*', 0, NULL, NULL FROM "#__extensions" WHERE "name" = 'com_content'; +SELECT 101, 'mainmenu', 'Home', 'home', '', 'home', 'index.php?option=com_content&view=featured', 'component', 1, 1, 1, "extension_id", 0, 1, '', 0, '{"featured_categories":[""],"layout_type":"blog","blog_class_leading":"","blog_class":"","num_leading_articles":"1","num_intro_articles":"3","num_links":"0","link_intro_image":"","orderby_pri":"","orderby_sec":"front","order_date":"","show_pagination":"2","show_pagination_results":"1","show_title":"","link_titles":"","show_intro":"","info_block_position":"","info_block_show_title":"","show_category":"","link_category":"","show_parent_category":"","link_parent_category":"","show_associations":"","show_author":"","link_author":"","show_create_date":"","show_modify_date":"","show_publish_date":"","show_item_navigation":"","show_vote":"","show_readmore":"","show_readmore_title":"","show_hits":"","show_tags":"","show_noauth":"","show_feed_link":"1","feed_summary":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_image_css":"","menu_text":1,"menu_show":1,"page_title":"","show_page_heading":"1","page_heading":"","pageclass_sfx":"","menu-meta_description":"","robots":""}', 51, 52, 1, '*', 0, NULL, NULL FROM "#__extensions" WHERE "name" = 'com_content'; SELECT setval('#__menu_id_seq', 102, false); -- diff --git a/installation/sql/postgresql/extensions.sql b/installation/sql/postgresql/extensions.sql index 0c91b45d1f..9b07752a7b 100644 --- a/installation/sql/postgresql/extensions.sql +++ b/installation/sql/postgresql/extensions.sql @@ -717,6 +717,66 @@ CREATE TABLE "#__privacy_consents" ( ); CREATE INDEX "#__privacy_consents_idx_user_id" ON "#__privacy_consents" ("user_id"); +-- +-- Table structure for table "#__privacy_cookies" +-- + +CREATE TABLE IF NOT EXISTS "#__privacy_cookies" ( + "id" serial NOT NULL, + "title" varchar(255) NOT NULL, + "alias" varchar(400) NOT NULL, + "cookie_name" varchar(255) NOT NULL, + "cookie_desc" varchar(255) NOT NULL, + "exp_period" varchar(20) NOT NULL, + "exp_value" integer DEFAULT 0 NOT NULL, + "catid" integer DEFAULT 0 NOT NULL, + "published" smallint DEFAULT 1 NOT NULL, + "ordering" integer DEFAULT 0 NOT NULL, + "created" timestamp without time zone NOT NULL, + "created_by" integer DEFAULT 0 NOT NULL, + "modified" timestamp without time zone NOT NULL, + "modified_by" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "#__privacy_cookies_idx_state" on "#__privacy_cookies" ("published"); +CREATE INDEX "#__privacy_cookies_idx_catid" on "#__privacy_cookies" ("catid"); +CREATE INDEX "#__privacy_cookies_idx_createdby" on "#__privacy_cookies" ("created_by"); + +-- +-- Table structure for table "#__privacy_scripts" +-- + +CREATE TABLE IF NOT EXISTS "#__privacy_scripts" ( + "id" serial NOT NULL, + "title" varchar(255) NOT NULL, + "alias" varchar(400) NOT NULL, + "position" integer DEFAULT 4 NOT NULL, + "type" integer DEFAULT 1 NOT NULL, + "code" text NOT NULL, + "catid" integer DEFAULT 0 NOT NULL, + "published" smallint DEFAULT 1 NOT NULL, + "ordering" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "#__privacy_scripts_idx_state" on "#__privacy_scripts" ("published"); +CREATE INDEX "#__privacy_scripts_idx_catid" on "#__privacy_scripts" ("catid"); + +-- +-- Table structure for table "#__privacy_cookie_consents" +-- + +CREATE TABLE IF NOT EXISTS "#__privacy_cookie_consents" ( + "id" serial NOT NULL, + "uuid" varchar(32) NOT NULL, + "ccuuid" varchar(64) NOT NULL, + "consent_opt_in" varchar(255) NOT NULL, + "consent_opt_out" varchar(255) NOT NULL, + "consent_date" timestamp without time zone NOT NULL, + "user_agent" varchar(150) NOT NULL, + "url" varchar(100) NOT NULL, + PRIMARY KEY ("id") +); + -- -- Table structure for table `#__redirect_links` -- @@ -788,7 +848,8 @@ INSERT INTO "#__action_logs_extensions" ("id", "extension") VALUES (16, 'com_templates'), (17, 'com_users'), (18, 'com_checkin'), -(19, 'com_scheduler'); +(19, 'com_scheduler'), +(20, 'com_privacy'); SELECT setval('#__action_logs_extensions_id_seq', 20, false); -- -------------------------------------------------------- @@ -830,7 +891,8 @@ INSERT INTO "#__action_log_config" ("id", "type_title", "type_alias", "id_holder (17, 'access_level', 'com_users.level', 'id' , 'title', '#__viewlevels', 'PLG_ACTIONLOG_JOOMLA'), (18, 'banner_client', 'com_banners.client', 'id', 'name', '#__banner_clients', 'PLG_ACTIONLOG_JOOMLA'), (19, 'application_config', 'com_config.application', '', 'name', '', 'PLG_ACTIONLOG_JOOMLA'), -(20, 'task', 'com_scheduler.task', 'id', 'title', '#__scheduler_tasks', 'PLG_ACTIONLOG_JOOMLA'); +(20, 'task', 'com_scheduler.task', 'id', 'title', '#__scheduler_tasks', 'PLG_ACTIONLOG_JOOMLA'), +(21, 'privacy_cookie', 'com_privacy.cookie', 'id', 'name', '#__privacy_cookies', 'PLG_ACTIONLOG_JOOMLA'); SELECT setval('#__action_log_config_id_seq', 21, false); diff --git a/installation/sql/postgresql/supports.sql b/installation/sql/postgresql/supports.sql index 8a00c79fff..77883a0c0f 100644 --- a/installation/sql/postgresql/supports.sql +++ b/installation/sql/postgresql/supports.sql @@ -71,9 +71,11 @@ INSERT INTO "#__categories" ("id", "asset_id", "parent_id", "lft", "rgt", "level (3, 28, 1, 3, 4, 1, 'uncategorised', 'com_banners', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1), (4, 29, 1, 5, 6, 1, 'uncategorised', 'com_contact', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1), (5, 30, 1, 7, 8, 1, 'uncategorised', 'com_newsfeeds', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1), -(7, 32, 1, 9, 10, 1, 'uncategorised', 'com_users', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1); +(7, 32, 1, 9, 10, 1, 'uncategorised', 'com_users', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1), +(8, 92, 1, 11, 12, 1, 'uncategorised', 'com_privacy', 'Uncategorised', 'uncategorised', '', '', 1, 1, '{"category_layout":"","image":"","mandatory":"0"}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1), +(9, 93, 1, 13, 14, 1, 'mandatory', 'com_privacy', 'Mandatory', 'mandatory', '', '', 1, 1, '{"category_layout":"","image":"","mandatory":"1"}', '', '', '{"author":"","robots":""}', 42, CURRENT_TIMESTAMP, 42, CURRENT_TIMESTAMP, 0, '*', 1); -SELECT setval('#__categories_id_seq', 8, false); +SELECT setval('#__categories_id_seq', 9, false); -- -- Table structure for table `#__content_types` diff --git a/libraries/src/Extension/ExtensionHelper.php b/libraries/src/Extension/ExtensionHelper.php index fd0ae4d019..b3552e5f74 100644 --- a/libraries/src/Extension/ExtensionHelper.php +++ b/libraries/src/Extension/ExtensionHelper.php @@ -283,6 +283,7 @@ class ExtensionHelper ['plugin', 'accessibility', 'system', 0], ['plugin', 'actionlogs', 'system', 0], ['plugin', 'cache', 'system', 0], + ['plugin', 'cookiemanager', 'system', 0], ['plugin', 'debug', 'system', 0], ['plugin', 'fields', 'system', 0], ['plugin', 'guidedtours', 'system', 0], diff --git a/package-lock.json b/package-lock.json index f0781a4090..dd16e5866f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "short-and-sweet": "^1.0.4", "skipto": "^4.1.7", "tinymce": "^6.5.1", + "vanilla-cookieconsent": "^2.9.1", "vue": "3.2.45", "vue-focus-lock": "^2.0.5", "vuex": "^4.1.0", @@ -9754,6 +9755,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vanilla-cookieconsent": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/vanilla-cookieconsent/-/vanilla-cookieconsent-2.9.1.tgz", + "integrity": "sha512-53SFENm1+nSOvuH8nEYRVB21gsYHnoglKD7Py9q1f9RSC4nkOOY5v3IE9rXh7g1vBLFnCtzlaFmJLm8/Y94iOQ==" + }, "node_modules/varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", @@ -17195,6 +17201,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "vanilla-cookieconsent": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/vanilla-cookieconsent/-/vanilla-cookieconsent-2.9.1.tgz", + "integrity": "sha512-53SFENm1+nSOvuH8nEYRVB21gsYHnoglKD7Py9q1f9RSC4nkOOY5v3IE9rXh7g1vBLFnCtzlaFmJLm8/Y94iOQ==" + }, "varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", @@ -17502,4 +17513,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 4a9b9fb8fe..118fba99ce 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "short-and-sweet": "^1.0.4", "skipto": "^4.1.7", "tinymce": "^6.5.1", + "vanilla-cookieconsent": "^2.9.1", "vue": "3.2.45", "vue-focus-lock": "^2.0.5", "vuex": "^4.1.0", @@ -113,4 +114,4 @@ "stylelint-scss": "^4.7.0", "terser": "^5.18.1" } -} +} \ No newline at end of file diff --git a/plugins/system/privacyconsent/src/AjaxHandlerConsent.php b/plugins/system/privacyconsent/src/AjaxHandlerConsent.php new file mode 100644 index 0000000000..40025bd4d5 --- /dev/null +++ b/plugins/system/privacyconsent/src/AjaxHandlerConsent.php @@ -0,0 +1,47 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Plugin\System\PrivacyConsent; + +use Joomla\CMS\Factory; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Offers ajax function to store selected user consent + * + * @since __DEPLOY_VERSION__ + */ +trait AjaxHandlerConsent +{ + /** + * AJAX Handler + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function onAjaxPrivacyconsent() + { + $cookieConsentsData = $this->getApplication()->input->get('data', '', 'STRING'); + + $cookieConsentsData = json_decode($cookieConsentsData); + $ccuuid = bin2hex(random_bytes(32)); + $cookieConsentsData->ccuuid = $ccuuid; + $cookieConsentsData->consent_date = Factory::getDate()->toSql(); + $cookieConsentsData->user_agent = $_SERVER['HTTP_USER_AGENT']; + + $this->getDatabase()->insertObject('#__privacy_consents', $cookieConsentsData); + + return $ccuuid; + } +} diff --git a/plugins/system/privacyconsent/src/ConsentBannerContentLoader.php b/plugins/system/privacyconsent/src/ConsentBannerContentLoader.php new file mode 100644 index 0000000000..819d267812 --- /dev/null +++ b/plugins/system/privacyconsent/src/ConsentBannerContentLoader.php @@ -0,0 +1,367 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Plugin\System\PrivacyConsent; + +use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\GenericEvent; +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Router\Route; +use Joomla\Event\Event; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Loads scripts and data for the consent banner + * + * @since __DEPLOY_VERSION__ + */ +trait ConsentBannerContentLoader +{ + /** + * @var \Joomla\Database\DatabaseDriver + * + * @since __DEPLOY_VERSION__ + */ + protected $db; + + /** + * Not mandatory scripts loaded in the head of the document are marked and loaded after user gave consent only. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onBeforeCompileHead() + { + if (!$this->getApplication()->isClient('site')) { + return; + } + + /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */ + $wa = $this->getApplication()->getDocument()->getWebAssetManager(); + $assets = $wa->getAssets('script'); + + if (count($assets) === 0) { + return; + } + + $this->markScripts($assets); + + // Finally load banner content with assets etc. + $this->loadCoockieBannerContent(); + } + + /** + * Collects data to build up and display the consent banner. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function loadCoockieBannerContent() + { + $document = $this->getApplication()->getDocument(); + $context = $this->getApplication()->getInput()->get('option') . '.' . $this->getApplication()->getInput()->get('view'); + + $event = new GenericEvent( + 'onLoadCookieCategories', + [ + 'context' => $context, + 'cookiecategories' => [], + ] + ); + $this->getDispatcher()->dispatch('onLoadCookieCategories', $event); + $document->addScriptOptions('plg_system_privacyconsent.categories', $event->getArgument('cookiecategories')); + + $event = new GenericEvent( + 'onLoadCookieScripts', + [ + 'context' => $context, + 'cookiescripts' => [], + ] + ); + $this->getDispatcher()->dispatch('onLoadCookieScripts', $event); + $document->addScriptOptions('plg_system_privacyconsent.scripts', $event->getArgument('cookiescripts')); + + $event = new GenericEvent( + 'onLoadCookies', + [ + 'context' => $context, + 'cookies' => [], + ] + ); + $this->getDispatcher()->dispatch('onLoadCookies', $event); + $document->addScriptOptions('plg_system_privacyconsent.cookies', $event->getArgument('cookies')); + + $this->setConfig(); + $this->loadTexts(); + $this->loadAssets(); + } + + /** + * Loads CSS and javascript files by the WebAssetManager + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function loadAssets() + { + $wa = $this->getApplication()->getDocument()->getWebAssetManager(); + $wa->registerAndUseScript( + 'plg_system_privacyconsent.script', + 'plg_system_privacyconsent/privacyconsent.min.js', + ['dependencies' => ['cookieconsent']], + ['defer' => true], + ); + $wa->registerAndUseStyle( + 'plg_system_privacyconsent.style', + 'plg_system_privacyconsent/privacyconsent.min.css', + ['dependencies' => ['cookieconsent']], + ['defer' => true], + ); + } + + /** + * Set texts in the current language for javascript + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function loadTexts() + { + $this->getApplication()->getLanguage()->load('com_privacyconsent', JPATH_ADMINISTRATOR); + + Text::script('PLG_SYSTEM_PRIVACYCONSENT_BANNER_TITLE'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_BANNER_DESC'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_ACCEPT_ALL'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_ACCEPT_SELECTED'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_REJECT_ALL'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_BANNER_BTN_SETTINGS'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_TITLE'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_SAVE'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_ACCEPT_ALL'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_REJECT_ALL'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_SETTINGS_BTN_CLOSE'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL1'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL2'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL3'); + Text::script('PLG_SYSTEM_PRIVACYCONSENT_TABLE_HEADERS_COL4'); + Text::script('COM_PRIVACY_PREVIEW_BUTTON_TEXT'); + Text::script('COM_PRIVACY_VIEW_COOKIE_POLICY'); + } + + /** + * Params are prepared for using them in the js cookie consent banner + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function setConfig() + { + $params = ComponentHelper::getParams('com_privacyconsent'); + + $cookieManagerConfig = [ + 'consent_modal' => [ + 'position_x' => $params->get('consent_modal_position_x', 'center'), + 'position_y' => $params->get('consent_modal_position_y', 'bottom'), + 'transition' => $params->get('consent_modal_transition', 'zoom'), + 'layout' => $params->get('consent_modal_layout', 'box'), + 'swap_buttons' => $params->get('consent_modal_swap_buttons', '0'), + 'primary_button_role' => $params->get('consent_modal_primary_button_role', 'accept_all'), + 'secondary_button_role' => $params->get('consent_modal_secondary_button_role', 'accept_necessary'), + ], + 'settings_modal' => [ + 'position' => $params->get('settings_modal_position', 'left'), + 'transition' => $params->get('settings_modal_transition', 'slide'), + 'layout' => $params->get('settings_modal_layout', 'box'), + ], + ]; + $cookieManagerConfig['expiration'] = $params->get('consent_expiration', 30); + $cookieManagerConfig['delay'] = $params->get('delay', 0); + $cookieManagerConfig['force_consent'] = $params->get('force_consent', 0); + $cookieManagerConfig['mode'] = $params->get('mode', 'opt-in'); + $cookieManagerConfig['hide_from_bots'] = $params->get('hide_from_bots', 1); + $cookieManagerConfig['remove_cookie_tables'] = $params->get('remove_cookie_tables', 0); + $cookieManagerConfig['cookie_same_site'] = $params->get('cookie_same_site', 'Lax'); + + $menuitem = $this->getApplication()->getMenu()->getItem($params->get('policylink', '')); + if ($menuitem) { + $cookieManagerConfig['policylink'] = HTMLHelper::_('link', Route::_($menuitem->link)); + } + + $this->getApplication()->getDocument()->addScriptOptions('plg_system_privacyconsent.config', $cookieManagerConfig); + } + + /** + * Additional scripts in the document header are marked with data-cookiecategory="" and type="text/plain" + * Necessary to block them first until user accepted their category usage. + * Scripts from /media/system and /media/templates are marked with category "mandatory" + * + * @param WebAssetItem[] $assets array of web assets to mark + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function markScripts(array $assets) + { + $scripts = $this->getCookiemanagerScripts(); + + foreach ($assets as $asset) { + $uri = $asset->getUri(); + $startOfName = strrpos($uri, '/'); + $assetName = $startOfName === false ? $uri : substr($uri, $startOfName + 1); + $isMandatory = str_contains($uri, '/media/system/') || str_contains($uri, '/media/templates/'); + $uncategorized = array_key_exists($assetName, $scripts) === false; + $category = $uncategorized ? 'unknown' : $scripts[$assetName]; + $type = $asset->getAttribute('type'); + + $asset->setAttribute('data-cookiecategory', $isMandatory ? 'mandatory' : $category); + + if (!$isMandatory) { + if (!empty($type)) { + $asset->setAttribute('data-cookiemanager-old-type', $type); + if (!str_contains($type, 'json')) { + $asset->setAttribute('type', 'text/plain'); + } + } + } + } + } + + /** + * Loads all cookiemanager scripts with title and category alias + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + private function getCookiemanagerScripts() + { + $db = $this->db; + + $query = $db->getQuery(true) + ->select($db->quoteName(['s.title', 's.catid', 'c.alias'])) + ->from($db->quoteName('#__privacy_scripts', 's')) + ->join( + 'LEFT', + $db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('s.catid') + ); + return $db->setQuery($query)->loadAssocList('title', 'alias'); + } + + /** + * Add cookies to list under their categories in the cookie consent banner + * + * @param Event $event The event + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onLoadCookies(Event $event) + { + $eventCookies = $event->getArgument('cookies', []); + + $db = $this->db; + + $query = $db->getQuery(true) + ->select($db->quoteName(['c.id', 'c.alias', 'a.cookie_name', 'a.cookie_desc', 'a.exp_period', 'a.exp_value'])) + ->from($db->quoteName('#__categories', 'c')) + ->join( + 'RIGHT', + $db->quoteName('#__privacy_cookies', 'a') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid') . 'WHERE' . $db->quoteName('a.published') . ' = 1' + ) + ->order($db->quoteName('lft')); + $cookies = $db->setQuery($query)->loadObjectList(); + + $eventCookies = array_merge( + $eventCookies, + $cookies, + ); + + $event->setArgument('cookies', $eventCookies); + } + + /** + * Add categories of cookies to deny or allow with the cookie consent banner + * + * @param Event $event The event + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onLoadCookieCategories(Event $event) + { + $eventCategories = $event->getArgument('cookiecategories', []); + + $db = $this->db; + + $query = $db->getQuery(true) + ->select($db->quoteName(['id', 'title', 'alias', 'description', 'params'])) + ->from($db->quoteName('#__categories')) + ->where( + [ + $db->quoteName('extension') . ' = ' . $db->quote('com_privacy'), + $db->quoteName('published') . ' = 1', + ] + ) + ->order($db->quoteName('lft')); + $categories = $db->setQuery($query)->loadObjectList(); + + $eventCategories = array_merge( + $eventCategories, + $categories, + ); + + $event->setArgument('cookiecategories', $eventCategories); + } + + /** + * Loads information about the scripts the consent banner should display. + * + * @param Event $event The event + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onLoadCookieScripts(Event $event) + { + $eventScripts = $event->getArgument('cookiescripts', []); + + $db = $this->db; + + $query = $db->getQuery(true) + ->select($db->quoteName(['s.type', 's.position', 's.code', 's.catid'])) + ->from($db->quoteName('#__privacy_scripts', 's')) + ->where($db->quoteName('s.published') . ' = 1') + ->join( + 'LEFT', + $db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('s.catid') + ); + $scripts = $db->setQuery($query)->loadObjectList(); + + $eventScripts = array_merge( + $eventScripts, + $scripts, + ); + + $event->setArgument('cookiescripts', $eventScripts); + } +} diff --git a/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php b/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php index dee9174cc8..52c7833c97 100644 --- a/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php +++ b/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php @@ -29,6 +29,10 @@ use Joomla\Database\DatabaseAwareTrait; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\ParameterType; +use Joomla\Event\Priority; +use Joomla\Event\SubscriberInterface; +use Joomla\Plugin\System\PrivacyConsent\AjaxHandlerConsent; +use Joomla\Plugin\System\PrivacyConsent\ConsentBannerContentLoader; use Joomla\Utilities\ArrayHelper; use PHPMailer\PHPMailer\Exception as phpmailerException; @@ -41,11 +45,14 @@ * * @since 3.9.0 */ -final class PrivacyConsent extends CMSPlugin +final class PrivacyConsent extends CMSPlugin implements SubscriberInterface { use DatabaseAwareTrait; use UserFactoryAwareTrait; + use AjaxHandlerConsent; + use ConsentBannerContentLoader; + /** * Load the language file on instantiation. * @@ -54,6 +61,27 @@ final class PrivacyConsent extends CMSPlugin */ protected $autoloadLanguage = true; + /** + * Returns an array of events this subscriber will listen to. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function getSubscribedEvents(): array + { + if (Factory::getApplication()->isClient('site')) { + return [ + 'onBeforeCompileHead' => ['onBeforeCompileHead', Priority::LOW], + 'onLoadCookies' => 'onLoadCookies', + 'onLoadCookieCategories' => 'onLoadCookieCategories', + 'onLoadCookieScripts' => 'onLoadCookieScripts', + 'onAjaxPrivacyconsent' => 'onAjaxPrivacyconsent', + ]; + } + return []; + } + /** * Adds additional fields to the user editing form * @@ -665,6 +693,7 @@ private function invalidateExpiredConsents() return true; } + /** * Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp. * diff --git a/ruleset.xml b/ruleset.xml index dbe75cebc3..cb2703b2fc 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -48,7 +48,10 @@ administrator/components/com_menus/src/Model/MenuModel\.php administrator/components/com_newsfeeds/src/Table/NewsfeedTable\.php administrator/components/com_plugins/src/Model/PluginModel\.php + administrator/components/com_privacy/src/Table/CookieconsentTable\.php + administrator/components/com_privacy/src/Table/CookieTable\.php administrator/components/com_privacy/src/Table/RequestTable\.php + administrator/components/com_privacy/src/Table/ScriptTable\.php administrator/components/com_scheduler/src/Table/TaskTable\.php administrator/components/com_tags/src/Table/TagTable\.php administrator/components/com_templates/src/Model/StyleModel\.php