Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

202411 consentratio lp 135 #495

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/default/core.extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module:
diff: 0
dynamic_entity_reference: 0
dynamic_page_cache: 0
ecc_cct: 0
ecc_cludo_search: 0
ecc_content_moderation: 0
ecc_cookie_compliance: 0
Expand Down
1 change: 1 addition & 0 deletions config/default/ecc_cct.settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
enable: '1'
11 changes: 11 additions & 0 deletions web/modules/custom/ecc_cct/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Essex Co Co cookie consent statistics.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, what am I missing? I don't see it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The abbreviation is confusing. Change to Essex County Council or ECC, like you have in other places.


This module hooks into the events thown by the EU Cookie Compliance module.

Whenever consent is set, this module takes a simplified value of choice: i.e.
granted or denied, and sends it to an endpoint.

The endpoint controller writes the time and value to a custom table.

An administrator can access a simple aggregate report of choices. It calculates
the percentage of grants out of the total number of records.
8 changes: 8 additions & 0 deletions web/modules/custom/ecc_cct/config/schema/ecc_cct.schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Schema for the configuration files of the ECC Cookie Consent Tracker module.
ecc_cct.settings:
type: config_object
label: 'ECC Cookie Consent Tracker settings'
mapping:
enable:
type: string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not boolean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exact why is lost in the mists of history, but I'm guessing it's an artifact of drush generate:module. It really doesn't matter one way or the other. It's stored as a string, it's read as a string, it works. I don't see it as having much impact either way? I mean, we can change it, but it'll be change for change's sake.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generate may be quick but it's no guarantee of good code.

label: 'enable'
7 changes: 7 additions & 0 deletions web/modules/custom/ecc_cct/ecc_cct.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: 'ECC Cookie Consent Tracker'
type: module
description: 'Tracks cookie consent choices and stores them for reporting.'
package: Custom
core_version_requirement: ^10 || ^11
dependencies:
- eu_cookie_consent:eu_cookie_compliance
41 changes: 41 additions & 0 deletions web/modules/custom/ecc_cct/ecc_cct.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/**
* @file
* Install, update and uninstall functions for the ECC Cookie Consent Tracker module.
*/

/**
* Implements hook_schema().
*/
function ecc_cct_schema() {
Polynya marked this conversation as resolved.
Show resolved Hide resolved
$schema['ecc_cct_data'] = [
'description' => 'Contains custom cookie consent data.',
'fields' => [
'id' => [
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique record ID.',
],
'choice' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'size' => 'small',
'default' => 0,
'description' => 'The value of the choice.',
],
'timestamp' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'size' => 'big',
'default' => 0,
'description' => 'Date of choice.',
],
],
'primary key' => ['id'],
];

return $schema;
}
6 changes: 6 additions & 0 deletions web/modules/custom/ecc_cct/ecc_cct.libraries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Custom library for general purposes.
ecc_cct:
js:
js/ecc_cct.js: {}
dependencies:
- core/drupalSettings
14 changes: 14 additions & 0 deletions web/modules/custom/ecc_cct/ecc_cct.links.menu.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Simple link.
ecc_cct.report:
title: Consent Statistics
description: Consent Statistics.
menu_name: administration
route_name: ecc_cct.report
parent: system.admin_reports
weight: 10

ecc_cct.settings:
title: Essex Cookie Consent Tracker
parent: system.admin_config_system
route_name: ecc_cct.settings
weight: 10
17 changes: 17 additions & 0 deletions web/modules/custom/ecc_cct/ecc_cct.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

use \Drupal\Core\Form\FormStateInterface;

/**
* @file
* Primary module hooks for ECC Cookie Consent Tracker module.
*/

/**
* Implements hook_preprocess_html().
*/
function ecc_cct_preprocess_html(&$vars) {
if ($vars['root_path'] !== 'admin') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to exclude the same paths as EU Cookie Compliance

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes as follows:

  1. using eu cookie settings for excluded paths
  2. using dependency injection for db service access
  3. using config() from controllerbase to access config
  4. using #config_target instead of default_value
  5. js is printing nothing to console now
  6. changed config schema to use integer as integer is what's actually used (boolean makes radio button choke)

$vars['#attached']['library'][] = 'ecc_cct/ecc_cct';
}
}
24 changes: 24 additions & 0 deletions web/modules/custom/ecc_cct/ecc_cct.routing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
ecc_cct.log:
path: '/ecc-cct/js/log/{choice}'
defaults:
_title: 'Content Log'
_controller: '\Drupal\ecc_cct\Controller\ConsentLog:log'
requirements:
_permission: 'access content'
choice: '\d+' # Restricts 'choice' to be numeric only

ecc_cct.report:
path: 'admin/reports/consent'
defaults:
_title: 'Consent Statistics'
_controller: '\Drupal\ecc_cct\Controller\EccCctController:build'
requirements:
_permission: 'access site reports'

ecc_cct.settings:
path: '/admin/config/system/cookie-consent-tracker'
defaults:
_title: 'Cookie Consent Tracker'
_form: 'Drupal\ecc_cct\Form\SettingsForm'
requirements:
_permission: 'administer site configuration'
43 changes: 43 additions & 0 deletions web/modules/custom/ecc_cct/js/ecc_cct.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@


(function ($, Drupal, drupalSettings, cookies) {

Drupal.behaviors.eccCCT = {
attach: function (context) {
var count = 0;
var postPreferencesLoadHandler = function (response) {
// The event usually fires twice: once on initial click, then
// preferences are taken into account and it fires again.
// We can't reasonably capture dithering users who change choices
// several times per page load.
count++;
if (count === 2) {
console.log(response);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the console logs or add a debug mode so we can control if they are written.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.log removed, PR updated.

if (response.currentStatus === 'granted') {
ecc_cct_log(1);
}
else if (response.currentStatus === 'denied') {
ecc_cct_log(2);
}
}
};
Drupal.eu_cookie_compliance('postStatusSave', postPreferencesLoadHandler);



async function ecc_cct_log(choice) {
const response = await fetch('/ecc-cct/js/log/' + choice).catch(error => {
console.error('Error sending integer:', error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do users need to see anything in the console?

});


if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
else {
console.log(response);
}
}
}
}
})(jQuery, Drupal, drupalSettings, window.Cookies);
51 changes: 51 additions & 0 deletions web/modules/custom/ecc_cct/src/Controller/ConsentLog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Drupal\ecc_cct\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

/**
* Returns responses for ECC Cookie Consent Tracker routes.
*/
final class ConsentLog extends ControllerBase {

/**
* Handles requests to /ecc-cct/js/log/{$choice}.
*
* @param int $choice
* The dynamic ID passed to the route.
* @param \Symfony\Component\HttpFoundation\Request $request
* The HTTP request object.
*
* @return \Symfony\Component\HttpFoundation\Response
* A response object.
*/
public function log(int $choice, Request $request): Response {
// If this isn't turned on, bail.
if (!\Drupal::config('ecc_cct.settings')->get('enable')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be faster (one database read?) to use a State setting instead of config. Either way use the methods state() or config() in ControllerBase.

throw new ServiceUnavailableHttpException(NULL, t('Service unavailable'));
}
// Otherwise, let's write some data.
$content = [
'status' => 'success',
'message' => "Received ID: $choice",
];

/** @var \Drupal\Core\Database\Connection $connection */
$connection = \Drupal::service('database');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use a content entity instead of directly accessing the database table. It would make CRUD much easier.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah. The idea is to make this as minimalistic as possible, with the least overhead. Entities come with overhead. The DB write here couldn't be simpler, and the data extraction is a single SQL query. Entities and all of Drupal's APIs would be needless expense. In fact, we only ever want to do create and read, never update nor delete. We're doing just enough, and no more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Don't call services like this. Use create() for dependency injection.

$result = $connection->insert('ecc_cct_data')
->fields([
'choice' => $choice,
'timestamp' => time(),
])
->execute();

// Maybe long term, we don't care about a response...
// but just in case, for testing, let's leave it in.
return new Response(json_encode($content), 200, ['Content-Type' => 'application/json']);
}
}
32 changes: 32 additions & 0 deletions web/modules/custom/ecc_cct/src/Controller/EccCctController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Drupal\ecc_cct\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
* Returns responses for ECC Cookie Consent Tracker routes.
*/
final class EccCctController extends ControllerBase {

/**
* Builds the response.
*/
public function build(): array {
$sql = "SELECT (COUNT(CASE WHEN choice = 1 THEN 1 END) / COUNT(*)) * 100 AS optin FROM {ecc_cct_data};";
$database = \Drupal::database();
$query = $database->query($sql);
$result = $query->fetchAll();

$stat = $result[0]->optin;
$build['content'] = [
'#type' => 'item',
'#markup' => $this->t($stat . '% of people opted into cookies.'),
];

return $build;
}

}
55 changes: 55 additions & 0 deletions web/modules/custom/ecc_cct/src/Form/SettingsForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Drupal\ecc_cct\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

/**
* Configure ECC Cookie Consent Tracker settings for this site.
*/
final class SettingsForm extends ConfigFormBase {

/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'ecc_cct_settings';
}

/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return ['ecc_cct.settings'];
}

/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form['enable'] = [
'#type' => 'radios',
'#title' => $this->t('Enable tracking'),
'#options' => [
0 => $this->t('Disabled'),
1 => $this->t('Enabled'),
],
'#default_value' => $this->config('ecc_cct.settings')->get('enable'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add #config_target and you should be able to remove default_value and the submitForm method.

];
return parent::buildForm($form, $form_state);
}

/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$this->config('ecc_cct.settings')
->set('enable', $form_state->getValue('enable'))
->save();
parent::submitForm($form, $form_state);
}

}