Skip to content

Commit

Permalink
feat: add consent management feature
Browse files Browse the repository at this point in the history
  • Loading branch information
BernhardBaumrock committed Mar 30, 2023
1 parent 43cc908 commit b5cab3c
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 15 deletions.
88 changes: 88 additions & 0 deletions RockFrontend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"use strict";

// consent manager
(() => {
function Consent() {}
var C = new Consent();

// array loop helper
Consent.prototype.each = function (items, callback) {
for (var i = 0; i < items.length; i++) callback(items[i]);
};

// get object from localstorage
Consent.prototype.getStorage = function () {
let data = localStorage.getItem("rockfrontend-consent") || "{}";
return JSON.parse(data);
};

// get enabled state for given item
Consent.prototype.isEnabled = function (name) {
return this.getStorage()[name];
};

// init all containers and checkboxes
Consent.prototype.init = function () {
// show containers for enabled services
let show = document.querySelectorAll("[data-rfc-show]");
this.each(show, function (container) {
let name = container.getAttribute("data-rfc-show");
if (!C.isEnabled(name)) return;
container.removeAttribute("hidden");
let el = container.querySelector("[data-src]");
let src = el.getAttribute("data-src");
el.setAttribute("src", src);
});

// hide alternate containers for enabled services
let hide = document.querySelectorAll("[data-rfc-hide]");
this.each(hide, function (container) {
let name = container.getAttribute("data-rfc-hide");
if (C.isEnabled(name)) {
container.setAttribute("hidden", true);
} else {
container.removeAttribute("hidden");
}
});

// toggle checkboxes based on enabled state
let checkboxes = document.querySelectorAll(".rf-consent-checkbox");
this.each(checkboxes, (cb) => {
let name = cb.getAttribute("data-name");
if (this.isEnabled(name)) {
cb.setAttribute("checked", "checked");
} else {
cb.removeAttribute("checked");
}
});
};

// save consent state to localstorage
Consent.prototype.save = function (name, value) {
let storage = this.getStorage();
storage[name] = value;
localStorage.setItem("rockfrontend-consent", JSON.stringify(storage));
};

// monitor checkbox changes
document.addEventListener("change", function (e) {
let cb = e.target;
if (!cb.classList.contains("rf-consent-checkbox")) return;
let name = cb.getAttribute("data-name");
C.save(name, cb.checked);
C.init();
});

// monitor clicks on consent buttons
document.addEventListener("click", (e) => {
let el = e.target.closest("[rfc-allow]");
if (!el) return;
e.preventDefault();
let name = el.getAttribute("rfc-allow");
C.save(name, true);
C.init();
});

RockFrontend.Consent = C;
C.init();
})();
2 changes: 2 additions & 0 deletions RockFrontend.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 63 additions & 2 deletions RockFrontend.module.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Latte\Bridges\Tracy\LattePanel;
use Latte\Engine;
use Latte\Runtime\Html;
use RockFrontend\Asset;
use RockFrontend\LiveReload;
use RockFrontend\Manifest;
use RockFrontend\ScriptsArray;
Expand Down Expand Up @@ -226,6 +227,16 @@ function (HookEvent $event) {
$this->js("livereloadSecret", $secret);
}

// load RockFrontend frontend js file
$file = __DIR__ . "/RockFrontend.js";
if ($this->wire->config->debug) {
// load the non-minified script
$this->scripts()->add($file, "defer");
// when logged in as superuser we make sure to create the minified
// file even if the non-minified version is used.
if ($this->wire->user->isSuperuser()) $this->minifyFile($file);
} else $this->scripts()->add($this->minifyFile($file), "defer");

// load alfred?
if ($this->loadAlfred()) {
$this->js("rootUrl", $this->wire->config->urls->root);
Expand Down Expand Up @@ -257,8 +268,8 @@ function (HookEvent $event) {

// at the very end we inject the js variables
$assets = '';
$json = count($this->js) ? json_encode($this->js) : '';
if ($json) $assets .= "\n <script>let RockFrontend = $json</script>";
$json = count($this->js) ? json_encode($this->js) : '{}';
$assets .= "<script>let RockFrontend = $json</script>";

// check if assets have already been added
// if not we inject them at the end of the <head>
Expand All @@ -272,6 +283,39 @@ function (HookEvent $event) {
);
}

/**
* Render a portion of HTML that needs consent from the user.
*
* This will replace all "src" attributes by "data-src" attributes.
* All scripts will therefore only be loaded when the user clicks on the
* consent button.
*
* Usage:
* $rockfrontend->consent(
* 'youtube',
* '<iframe src=...',
* '<a href=# rfc-allow=youtube>Allow YouTube-Player on this website</a>'
* );
*
* You can also render files instead of markup:
* $rockfrontend->consent(
* 'youtube'
* 'your youtube embed code',
* 'sections/youtube-consent.latte'
* );
*/
public function consent($name, $enabled, $disabled = null)
{
$enabled = str_replace(" src=", " data-src=", $enabled);
$enabled = "<div data-rfc-show='$name' hidden>$enabled</div>";
if ($disabled) {
$file = $this->getFile($disabled);
if ($file) $disabled = $this->render($file);
$disabled = "<div data-rfc-hide='$name' hidden>$disabled</div>";
}
return $this->html($enabled . $disabled);
}

public function ___addAlfredStyles()
{
$this->styles()->add($this->path . "Alfred.css", "", ['minify' => false]);
Expand Down Expand Up @@ -1308,6 +1352,23 @@ private function migrateOgImage()
$rm->addFieldToTemplate(self::field_ogimage, 'home');
}

/**
* Minify file and return path of minified file
*/
public function minifyFile($file, $minFile = null): string
{
$file = new Asset($file);
if (!$minFile) $minFile = $file->minPath();
$minFile = new Asset($minFile);
if ($minFile->m < $file->m) {
require_once __DIR__ . "/vendor/autoload.php";
if ($file->ext == 'js') $minify = new \MatthiasMullie\Minify\JS($file);
else $minify = new \MatthiasMullie\Minify\CSS($file);
$minify->minify($minFile->path);
}
return $minFile->path;
}

/**
* Apply postCSS rules to given string
*/
Expand Down
52 changes: 52 additions & 0 deletions TextformatterRockFrontend.module.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace ProcessWire;

/**
* @author Bernhard Baumrock, 28.03.2023
* @license Licensed under MIT
* @link https://www.baumrock.com
*/
class TextformatterRockFrontend extends Textformatter implements ConfigurableModule
{

public $checkboxclass = 'uk-checkbox';

public static function getModuleInfo()
{
return [
'title' => 'RockFrontend Textformatter',
'version' => '1.0.0',
'summary' => 'Textformatter to manage consent',
];
}

public function format(&$str)
{
if (strpos($str, "[rf-consent=") === false) return;
$str = preg_replace_callback("/\[rf-consent=(.*?)\](.*?)\[\/rf-consent\]/", function ($matches) {
$name = $matches[1];
$str = $matches[2];
return "<label>
<input type='checkbox' class='rf-consent-checkbox {$this->checkboxclass}' data-name='$name'>
$str
</label>";
}, $str);
}


/**
* Config inputfields
* @param InputfieldWrapper $inputfields
*/
public function getModuleConfigInputfields($inputfields)
{
$inputfields->add([
'type' => 'text',
'name' => 'checkboxclass',
'label' => 'Checkbox Class',
'value' => $this->checkboxclass,
'notes' => 'Here you can adjust the class that is applied to consent checkboxes. Default is "uk-checkbox".',
]);
}
}
27 changes: 27 additions & 0 deletions classes/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,27 @@ public function isExternal()
return false;
}

public function isJS()
{
return $this->ext == 'js';
}

public function isMin()
{
return substr($this->basename, -7) == '.min.js'
or substr($this->basename, -8) == '.min.css';
}

/**
* Return current path with minified extension
* foo.js --> foo.min.js
*/
public function minPath()
{
$ext = $this->isJS() ? '.min.js' : '.min.css';
return $this->dir . $this->filename . $ext;
}

public function rockfrontend(): RockFrontend
{
return $this->wire->modules->get('RockFrontend');
Expand Down Expand Up @@ -105,6 +126,11 @@ public function setPath($path)
}
}

public function __toString()
{
return $this->path;
}

public function __debugInfo()
{
return [
Expand All @@ -119,6 +145,7 @@ public function __debugInfo()
'ext' => $this->ext,
'comment' => $this->comment,
'debug' => $this->debug,
'isMin()' => $this->isMin(),
];
}
}
19 changes: 6 additions & 13 deletions classes/AssetsArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,8 @@ public function minifyAuto(Asset $asset): Asset
// if no unminified file exists we return the asset as is
if (!is_file($nomin)) return $asset;

// a non-minified file exists, so we check if it has been updated
if ($this->rockfrontend()->isNewer($nomin, $min)) {
require_once __DIR__ . "/../vendor/autoload.php";
if ($asset->ext == 'js') $minify = new \MatthiasMullie\Minify\JS($nomin);
else $minify = new \MatthiasMullie\Minify\CSS($nomin);
$minify->minify($min);
}
// else we minify the file if it has changed
$this->rockfrontend()->minifyFile($nomin, $min);

return $asset;
}
Expand All @@ -167,12 +162,10 @@ public function minifyForced(Asset $asset): Asset
else $min = $asset->dir . $asset->filename . ".min.js";

$asset = new Asset($min, $asset->suffix);
if ($this->rockfrontend()->isNewer($nomin, $min)) {
require_once __DIR__ . "/../vendor/autoload.php";
if ($asset->ext == 'js') $minify = new \MatthiasMullie\Minify\JS($nomin);
else $minify = new \MatthiasMullie\Minify\CSS($nomin);
$minify->minify($min);
}

// else we minify the file if it has changed
$this->rockfrontend()->minifyFile($nomin, $min);

return $asset;
}

Expand Down

0 comments on commit b5cab3c

Please sign in to comment.