From 4a84ed0018df7fd67000404bb5ef8a7ca50509c1 Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" Date: Mon, 22 Jun 2020 11:31:54 +0100 Subject: [PATCH] Add new "Consent Banner" plugin, and update Google Analytics plugin to use it --- plugins/tiddlywiki/consent-banner/banner.tid | 28 +++++++ .../consent-banner/buttons/accept.tid | 6 ++ .../consent-banner/buttons/decline.tid | 6 ++ plugins/tiddlywiki/consent-banner/config.tid | 42 ++++++++++ .../consent-banner/config/buttons.multids | 8 ++ .../config/cookie-consent-required.tid | 2 + .../config/greeting-message.tid | 19 +++++ plugins/tiddlywiki/consent-banner/plugin.info | 6 ++ plugins/tiddlywiki/consent-banner/readme.tid | 44 +++++++++++ plugins/tiddlywiki/consent-banner/startup.js | 68 ++++++++++++++++ plugins/tiddlywiki/consent-banner/styles.tid | 79 +++++++++++++++++++ .../consent-banner/youtube-macros.tid | 19 +++++ .../googleanalytics/googleanalytics.js | 50 +++++++++--- plugins/tiddlywiki/googleanalytics/readme.tid | 2 + 14 files changed, 366 insertions(+), 13 deletions(-) create mode 100644 plugins/tiddlywiki/consent-banner/banner.tid create mode 100644 plugins/tiddlywiki/consent-banner/buttons/accept.tid create mode 100644 plugins/tiddlywiki/consent-banner/buttons/decline.tid create mode 100644 plugins/tiddlywiki/consent-banner/config.tid create mode 100644 plugins/tiddlywiki/consent-banner/config/buttons.multids create mode 100644 plugins/tiddlywiki/consent-banner/config/cookie-consent-required.tid create mode 100644 plugins/tiddlywiki/consent-banner/config/greeting-message.tid create mode 100644 plugins/tiddlywiki/consent-banner/plugin.info create mode 100644 plugins/tiddlywiki/consent-banner/readme.tid create mode 100644 plugins/tiddlywiki/consent-banner/startup.js create mode 100644 plugins/tiddlywiki/consent-banner/styles.tid create mode 100644 plugins/tiddlywiki/consent-banner/youtube-macros.tid diff --git a/plugins/tiddlywiki/consent-banner/banner.tid b/plugins/tiddlywiki/consent-banner/banner.tid new file mode 100644 index 000000000..e9fcaba07 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/banner.tid @@ -0,0 +1,28 @@ +title: $:/plugins/tiddlywiki/consent-banner/banner +tags: $:/tags/PageTemplate + +\whitespace trim + +<$reveal state="$:/state/consent-banner/accepted" type="match" text="" tag="div"> + + + + + + diff --git a/plugins/tiddlywiki/consent-banner/buttons/accept.tid b/plugins/tiddlywiki/consent-banner/buttons/accept.tid new file mode 100644 index 000000000..69066b8b3 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/buttons/accept.tid @@ -0,0 +1,6 @@ +title: $:/plugins/tiddlywiki/consent-banner/buttons/accept +tags: $:/tags/ConsentBanner/Button + +<$button message="tm-consent-accept" class="tc-consent-button tc-consent-button-default tc-btn-invisible" tooltip={{$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/hint}}> +{{$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/caption}} + diff --git a/plugins/tiddlywiki/consent-banner/buttons/decline.tid b/plugins/tiddlywiki/consent-banner/buttons/decline.tid new file mode 100644 index 000000000..681512af8 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/buttons/decline.tid @@ -0,0 +1,6 @@ +title: $:/plugins/tiddlywiki/consent-banner/buttons/decline +tags: $:/tags/ConsentBanner/Button + +<$button message="tm-consent-decline" class="tc-consent-button tc-btn-invisible" tooltip={{$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/hint}}> +{{$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/caption}} + diff --git a/plugins/tiddlywiki/consent-banner/config.tid b/plugins/tiddlywiki/consent-banner/config.tid new file mode 100644 index 000000000..66ede01ce --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/config.tid @@ -0,0 +1,42 @@ +title: $:/plugins/tiddlywiki/consent-banner/config + +! [[Greeting Message|$:/config/plugins/tiddlywiki/consent-banner/greeting-message]] + +
+ +
+ +<$edit-text tiddler="$:/config/plugins/tiddlywiki/consent-banner/greeting-message" tag="textarea" class="tc-edit-texteditor"/> + +
+ + + +
+ +! Buttons + +|[[Accept caption|$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/caption]] |<$edit-text tiddler="$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/caption" tag="input"/> | +|[[Accept hint|$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/hint]] |<$edit-text tiddler="$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/hint" tag="input"/> | +|[[Decline caption|$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/caption]] |<$edit-text tiddler="$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/caption" tag="input"/> | +|[[Decline hint|$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/hint]] |<$edit-text tiddler="$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/hint" tag="input"/> | + +! [[Consent Accepted Status|$:/state/consent-banner/accepted]] + +Current status: {{$:/state/consent-banner/accepted}} (blank indicates that consent has not yet been granted or declined) + +<$button message="tm-consent-accept" tooltip={{$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/hint}}> +{{$:/config/plugins/tiddlywiki/consent-banner/buttons/accept/caption}} + + +<$button message="tm-consent-decline" tooltip={{$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/hint}}> +{{$:/config/plugins/tiddlywiki/consent-banner/buttons/decline/caption}} + + +<$button message="tm-consent-clear" tooltip={{$:/config/plugins/tiddlywiki/consent-banner/buttons/clear/hint}}> +{{$:/config/plugins/tiddlywiki/consent-banner/buttons/clear/caption}} + diff --git a/plugins/tiddlywiki/consent-banner/config/buttons.multids b/plugins/tiddlywiki/consent-banner/config/buttons.multids new file mode 100644 index 000000000..14f5a9993 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/config/buttons.multids @@ -0,0 +1,8 @@ +title: $:/config/plugins/tiddlywiki/consent-banner/buttons/ + +accept/caption: Accept +accept/hint: Accept cookies +clear/caption: Clear +clear/hint: Clear cookies +decline/caption: Decline +decline/hint: Decline cookies diff --git a/plugins/tiddlywiki/consent-banner/config/cookie-consent-required.tid b/plugins/tiddlywiki/consent-banner/config/cookie-consent-required.tid new file mode 100644 index 000000000..4d388bbe3 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/config/cookie-consent-required.tid @@ -0,0 +1,2 @@ +title: $:/config/cookie-consent-required +text: yes \ No newline at end of file diff --git a/plugins/tiddlywiki/consent-banner/config/greeting-message.tid b/plugins/tiddlywiki/consent-banner/config/greeting-message.tid new file mode 100644 index 000000000..38de3c9b1 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/config/greeting-message.tid @@ -0,0 +1,19 @@ +title: $:/config/plugins/tiddlywiki/consent-banner/greeting-message + +! Our use of cookies + +We use necessary cookies to make our site work. We’d also like to set optional analytics to help us improve it. We won’t set optional cookies unless you enable them. Using this tool will set a cookie on your device to remember your preferences. + +--- + +!! Necessary cookies + +Necessary cookies enable core functionality such as security, network management, and accessibility. You may disable these by changing your browser settings, but this may affect how the website functions. + +--- + +!! Analytics cookies + +We’d like to set non-essential cookies, such as Google Analytics, to help us to improve our website by collecting and reporting information on how you use it. The cookies collect information in a way that does not directly identify anyone. + +--- diff --git a/plugins/tiddlywiki/consent-banner/plugin.info b/plugins/tiddlywiki/consent-banner/plugin.info new file mode 100644 index 000000000..815290d90 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/plugin.info @@ -0,0 +1,6 @@ +{ + "title": "$:/plugins/tiddlywiki/consent-banner", + "name": "Consent Banner", + "description": "Consent banner for GDPR etc", + "list": "readme youtube config" +} diff --git a/plugins/tiddlywiki/consent-banner/readme.tid b/plugins/tiddlywiki/consent-banner/readme.tid new file mode 100644 index 000000000..16094080b --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/readme.tid @@ -0,0 +1,44 @@ +title: $:/plugins/tiddlywiki/consent-banner/readme + +! Introduction + +The ''consent-banner'' plugin helps make websites that are compliant with "cookie legislation" such as the [[EU General Data Protection Regulation|https://gdpr.eu/cookies/]]. + +! Overview + +This plugin presents a banner inviting the user to accept or reject cookies, keeping track of their consent in local storage so that the banner can be hidden on subsequent visits. Consent status is also available via a configuration tiddler so that it is possible to construct content that behaves differently depending upon whether consent has been granted. As an example, a macro is provided for embedding ~YouTube videos that automatically uses the youtube-nocookie.com variant of video URLs unless the user has accepted cookies. + +If the same wiki is opened in multiple tabs then once the warning has been accepted or declined in one tab then the other tabs will autonatically follow suit. + +Consent is automatically granted if the user logged in (ie the tiddler [[$:/status/IsLoggedIn]] is set to `yes`). + +Please note that using this plugin does not guarantee compliance with any particular legislation. You will need to understand the technical issues specific to your situation, and if necessary seek legal advice. + +! ~YouTube macro + +A simple macro for embedding ~YouTube videos is provided to show how to adapt content according to whether consent has been granted. It works by checking the tiddler [[$:/state/consent-banner/accepted]] for the following values: + +* ''empty or missing'' - the user has yet to accept or decline to give their consent +* `yes` - the user has granted consent +* `no` - the user has declined consent + +! Customising banner buttons + +The [["accept"|$:/plugins/tiddlywiki/consent-banner/buttons/accept]] and [["decline"|$:/plugins/tiddlywiki/consent-banner/buttons/decline]] buttons in the banner are individual tiddlers with the tag [[$:/tags/ConsentBanner/Button]], allowing them to be customised and extended. + +A common use case is to add a "login" button allowing users to login directly to bypass the banner. This could be implemented as a tiddler tagged [[$:/tags/ConsentBanner/Button]] with the following text: + +``` +<$button message="tm-login" class="tc-consent-button tc-btn-invisible"> +Login + +``` + +! Integration with other plugins + +Third party plugins that set cookies can configure themselves to defer setting cookies until the user grants consent. There are several parts to this mechanism: + +* The consent-banner plugin includes a shadow tiddler [[$:/config/cookie-consent-required]] with the text `yes`. The third-party plugin should inspect this tiddler at startup; if it is not set to "yes" then it can proceed to set tiddlers immediately +* Otherwise, the third-party plugin should listen for changes to the tiddler [[$:/state/consent-banner/accepted]] and only start setting cookies when and if the value changes to "yes" + +The [[Google Analytics plugin|https://github.com/Jermolene/TiddlyWiki5/tree/master/plugins/tiddlywiki/googleanalytics]] shows an example of how this mechanism can be implemented. diff --git a/plugins/tiddlywiki/consent-banner/startup.js b/plugins/tiddlywiki/consent-banner/startup.js new file mode 100644 index 000000000..a65db100b --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/startup.js @@ -0,0 +1,68 @@ +/*\ +title: $:/plugins/tiddlywiki/consent-banner/startup.js +type: application/javascript +module-type: startup + +Startup initialisation + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +// Export name and synchronous status +exports.name = "consent-banner"; +exports.platforms = ["browser"]; +exports.after = ["startup"]; +exports.synchronous = true; + +var CHECK_CONSENT_INTERVAL = 1000, // Milliseconds between checking local storage + IS_LOGGED_IN_TITLE = "$:/status/IsLoggedIn", + CONSENT_KEY = "COOKIE_CONSENT", // Local storage keyname + CONSENT_TITLE = "$:/state/consent-banner/accepted"; // "": undeclared, "yes": accepted, "no": declined + +exports.startup = function() { + var self = this, + consentState = "", + setConsentStatus = function(state) { + if(consentState !== state) { + consentState = state; + // Write to local storage + window.localStorage.setItem(CONSENT_KEY,state); + // Write to a state tiddler + $tw.wiki.addTiddler(new $tw.Tiddler({ + title: CONSENT_TITLE, + text: state + })); + } + }, + calculateConsentStatus = function() { + // Consent is implied for logged in users, otherwise we check local storage + return ($tw.wiki.getTiddlerText(IS_LOGGED_IN_TITLE) === "yes" && "yes") || window.localStorage.getItem(CONSENT_KEY) || ""; + }, + checkConsentStatus = function() { + setConsentStatus(calculateConsentStatus()); + if(consentState === "") { + pollConsentStatus(); + } + }, + pollConsentStatus = function() { + setTimeout(checkConsentStatus,CHECK_CONSENT_INTERVAL); + }; + // Set the current constant status + checkConsentStatus(); + // Listen for tm-clear-browser-storage messages + $tw.rootWidget.addEventListener("tm-consent-accept",function(event) { + setConsentStatus("yes"); + }); + $tw.rootWidget.addEventListener("tm-consent-decline",function(event) { + setConsentStatus("no"); + }); + $tw.rootWidget.addEventListener("tm-consent-clear",function(event) { + setConsentStatus(""); + }); +}; + +})(); diff --git a/plugins/tiddlywiki/consent-banner/styles.tid b/plugins/tiddlywiki/consent-banner/styles.tid new file mode 100644 index 000000000..f8c969f14 --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/styles.tid @@ -0,0 +1,79 @@ +title: $:/plugins/tiddlywiki/consent-banner/styles +tags: $:/tags/Stylesheet + +.tc-consent-backdrop { + z-index: 1999; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: rgba(0,0,0,0.2); +} + +.tc-consent-banner-left { + z-index: 2000; + position: fixed; + left: 0; + top: 0; + bottom: 0; + max-width: 500px; + overflow-y: auto; +} + +.tc-consent-banner { + padding: 1em; + background: #009677; + color: #fff; + box-shadow: 0 0 20px rgba(0,0,0,.2); +} + +.tc-consent-banner a.tc-tiddlylink-external { + text-decoration: underline; + color: #fff; + background-color: inherit; +} + +.tc-consent-banner a.tc-tiddlylink-external:visited { + color: #fff; + background-color: inherit; +} + +.tc-consent-banner hr { + clear: both; + padding: 0; + width: 100%; + overflow: hidden; + text-align: left; + border: 0 none; + margin: 24px 0; + height: 1px; + max-height: 1px; + background: rgba(255,255,255,.25); +} + +.tc-consent-buttons { + +} + +.tc-consent-button { + border: 1px solid #fff; + margin-right: 1em; + margin-top: 1em; + padding: 0.75em 1.5em; + color: #fff; + background: transparent; + font-weight: bold; +} + +.tc-consent-button:hover { + color: #009577; + border-color: #fff; + background: #fff; + opacity: .6; +} + +.tc-consent-button-default { + color: #009677; + background: #fff; +} \ No newline at end of file diff --git a/plugins/tiddlywiki/consent-banner/youtube-macros.tid b/plugins/tiddlywiki/consent-banner/youtube-macros.tid new file mode 100644 index 000000000..78fced22d --- /dev/null +++ b/plugins/tiddlywiki/consent-banner/youtube-macros.tid @@ -0,0 +1,19 @@ +title: $:/plugins/tiddlywiki/consent-banner/youtube +tags: $:/tags/Macro + +\define embed-video-with-consent(code) +<$reveal state="$:/state/consent-banner/accepted" type="match" text="yes" tag="div"> + + +<$reveal state="$:/state/consent-banner/accepted" type="nomatch" text="yes" tag="div"> + + +\end + +! Macro source + +<$codeblock code={{$:/plugins/tiddlywiki/consent-banner/youtube}}/> + +! Example + +<> diff --git a/plugins/tiddlywiki/googleanalytics/googleanalytics.js b/plugins/tiddlywiki/googleanalytics/googleanalytics.js index a3f48a321..1634e465b 100644 --- a/plugins/tiddlywiki/googleanalytics/googleanalytics.js +++ b/plugins/tiddlywiki/googleanalytics/googleanalytics.js @@ -17,20 +17,44 @@ exports.name = "google-analytics"; exports.platforms = ["browser"]; exports.synchronous = true; +var CONFIG_CONSENT_REQUIRED_TITLE = "$:/config/cookie-consent-required", + CONSENT_TITLE = "$:/state/consent-banner/accepted"; // "": undeclared, "yes": accepted, "no": declined + exports.startup = function() { - // getting parameters - var GA_ACCOUNT = $tw.wiki.getTiddlerText("$:/GoogleAnalyticsAccount","").replace(/\n/g,""), - GA_DOMAIN = $tw.wiki.getTiddlerText("$:/GoogleAnalyticsDomain","").replace(/\n/g,""); - if (GA_DOMAIN == "" || GA_DOMAIN == undefined) GA_DOMAIN = "auto"; - - // using ga "isogram" function - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - - ga('create', GA_ACCOUNT, GA_DOMAIN); - ga('send', 'pageview'); + var hasInitialised = false, + initialiseGoogleAnalytics = function() { + console.log("Initialising Google Analytics"); + hasInitialised = true; + var gaAccount = $tw.wiki.getTiddlerText("$:/GoogleAnalyticsAccount","").replace(/\n/g,""), + gaDomain = $tw.wiki.getTiddlerText("$:/GoogleAnalyticsDomain","auto").replace(/\n/g,""); + // Using ga "isogram" function + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + ga('create',gaAccount,gaDomain); + ga('send','pageview'); + }; + // Initialise now if consent isn't required + if($tw.wiki.getTiddlerText(CONFIG_CONSENT_REQUIRED_TITLE) !== "yes") { + initialiseGoogleAnalytics(); + } else { + // Or has been granted already + if($tw.wiki.getTiddlerText(CONSENT_TITLE) === "yes") { + initialiseGoogleAnalytics(); + } else { + // Or when our config tiddler changes + $tw.wiki.addEventListener("change",function(changes) { + if(changes[CONSENT_TITLE]) { + if(!hasInitialised && $tw.wiki.getTiddlerText(CONSENT_TITLE) === "yes") { + initialiseGoogleAnalytics(); + } + } + }); + } + } }; + + })(); diff --git a/plugins/tiddlywiki/googleanalytics/readme.tid b/plugins/tiddlywiki/googleanalytics/readme.tid index 8a94012eb..f10803be1 100644 --- a/plugins/tiddlywiki/googleanalytics/readme.tid +++ b/plugins/tiddlywiki/googleanalytics/readme.tid @@ -2,4 +2,6 @@ title: $:/plugins/tiddlywiki/googleanalytics/readme This plugin enables you to use Google Analytics to track access to your online TiddlyWiki document. Based upon the [[official Google code|https://developers.google.com/analytics/devguides/collection/analyticsjs]]. +By default, the user is not asked for permission before initialising Google Analytics. This plugin also optionally integrates with the "Consent Banner" plugin (also found in the official plugin library) so that Google Analytics is not initialised until the user grants explicit permission. + [[Source code|https://github.com/Jermolene/TiddlyWiki5/blob/master/plugins/tiddlywiki/googleanalytics]]